diff --git a/.github/ISSUE_TEMPLATE/z-apidocs-feedback.yml b/.github/ISSUE_TEMPLATE/z-apidocs-feedback.yml new file mode 100644 index 0000000000000..4ebe78e3dfa34 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/z-apidocs-feedback.yml @@ -0,0 +1,46 @@ +name: Learn feedback control. +description: | + ⛔ This template is hooked into the feedback control on Roslyn API documentation on docs.microsoft.com. It automatically fills in several fields for you. Don't use for other purposes. ⛔ +body: + - type: markdown + attributes: + value: "## Issue information" + - type: markdown + attributes: + value: Select the issue type, and describe the issue in the text box below. Add as much detail as needed to help us resolve the issue. + - type: dropdown + id: issue-type + attributes: + label: Type of issue + options: + - Typo + - Code doesn't work + - Missing information + - Outdated article + - Other (describe below) + validations: + required: true + - type: textarea + id: feedback + validations: + required: true + attributes: + label: Description + - type: markdown + attributes: + value: "## 🚧 Article information 🚧" + - type: markdown + attributes: + value: "*Don't modify the following fields*. They are automatically filled in for you. Doing so will disconnect your issue from the affected article. *Don't edit them*." + - type: input + id: pageUrl + validations: + required: true + attributes: + label: Page URL + - type: input + id: contentSourceUrl + validations: + required: true + attributes: + label: Content source URL diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f85f5cc650b82..ca796b609eafa 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -67,6 +67,19 @@ "problemMatcher": "$msCompile", "group": "build" }, + { + "label": "build Roslyn.sln", + "command": "dotnet", + "type": "shell", + "args": [ + "build", + "-p:RunAnalyzersDuringBuild=false", + "-p:GenerateFullPaths=true", + "Roslyn.sln" + ], + "problemMatcher": "$msCompile", + "group": "build" + }, { "label": "build current project", "type": "shell", diff --git a/Roslyn.sln b/Roslyn.sln index b682dc262a06b..848adc0f76206 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31319.15 @@ -363,8 +362,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Lang EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.Debugger", "src\Tools\ExternalAccess\Debugger\Microsoft.CodeAnalysis.ExternalAccess.Debugger.csproj", "{655A5B07-39B8-48CD-8590-8AC0C2B708D8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.VisualStudio.Setup.ServiceHub.Desktop.Config", "src\Setup\DevDivVsix\ServiceHubConfig\Roslyn.VisualStudio.Setup.ServiceHub.Desktop.Config.csproj", "{3D33BBFD-EC63-4E8C-A714-0A48A3809A87}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.FSharp.UnitTests", "src\Tools\ExternalAccess\FSharpTest\Microsoft.CodeAnalysis.ExternalAccess.FSharp.UnitTests.csproj", "{BFFB5CAE-33B5-447E-9218-BDEBFDA96CB5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.Apex", "src\Tools\ExternalAccess\Apex\Microsoft.CodeAnalysis.ExternalAccess.Apex.csproj", "{FC32EF16-31B1-47B3-B625-A80933CB3F29}" @@ -544,18 +541,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Feat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Features.Test.Utilities", "src\Features\TestUtilities\Microsoft.CodeAnalysis.Features.Test.Utilities.csproj", "{5762E483-75CE-4328-A410-511F30737712}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0F3118AE-8D36-4384-8E80-BD6566365305}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{47D004BE-F797-430E-8A18-4B0CDFD56643}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics", "src\Tools\ExternalAccess\VisualDiagnostics\Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj", "{6D819E80-BA2F-4317-8368-37F8F4434D3A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Replay", "src\Tools\Replay\Replay.csproj", "{DB96C25F-39A9-4A6A-92BC-D1E42717308F}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CommonLanguageServerProtocol.Framework.Shared", "src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.shproj", "{64EADED3-4B5D-4431-BBE5-A4ABA1C38C00}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CommonLanguageServerProtocol.Framework.Binary", "src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework.Binary\Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj", "{730CADBA-701F-4722-9B6F-1FCC0DF2C95D}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests", "src\Compilers\CSharp\Test\Emit3\Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests.csproj", "{4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SemanticSearch", "SemanticSearch", "{52ABB0E4-C3A1-4897-B51B-18EDA83F5D20}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticSearch.BuildTask", "src\Tools\SemanticSearch\BuildTask\SemanticSearch.BuildTask.csproj", "{FCE88BBD-9BBD-4871-B9B0-DE176D73A6B0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticSearch.ReferenceAssemblies", "src\Tools\SemanticSearch\ReferenceAssemblies\SemanticSearch.ReferenceAssemblies.csproj", "{EDEF898A-CEFA-4151-8168-D0231A602093}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticSearch.BuildTask.UnitTests", "src\Tools\SemanticSearch\Tests\SemanticSearch.BuildTask.UnitTests.csproj", "{D817E3CE-F603-499B-B02A-7DECD017B170}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1106,10 +1107,6 @@ Global {655A5B07-39B8-48CD-8590-8AC0C2B708D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {655A5B07-39B8-48CD-8590-8AC0C2B708D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {655A5B07-39B8-48CD-8590-8AC0C2B708D8}.Release|Any CPU.Build.0 = Release|Any CPU - {3D33BBFD-EC63-4E8C-A714-0A48A3809A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3D33BBFD-EC63-4E8C-A714-0A48A3809A87}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3D33BBFD-EC63-4E8C-A714-0A48A3809A87}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3D33BBFD-EC63-4E8C-A714-0A48A3809A87}.Release|Any CPU.Build.0 = Release|Any CPU {BFFB5CAE-33B5-447E-9218-BDEBFDA96CB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BFFB5CAE-33B5-447E-9218-BDEBFDA96CB5}.Debug|Any CPU.Build.0 = Debug|Any CPU {BFFB5CAE-33B5-447E-9218-BDEBFDA96CB5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1358,18 +1355,30 @@ Global {5762E483-75CE-4328-A410-511F30737712}.Debug|Any CPU.Build.0 = Debug|Any CPU {5762E483-75CE-4328-A410-511F30737712}.Release|Any CPU.ActiveCfg = Release|Any CPU {5762E483-75CE-4328-A410-511F30737712}.Release|Any CPU.Build.0 = Release|Any CPU + {6D819E80-BA2F-4317-8368-37F8F4434D3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D819E80-BA2F-4317-8368-37F8F4434D3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D819E80-BA2F-4317-8368-37F8F4434D3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D819E80-BA2F-4317-8368-37F8F4434D3A}.Release|Any CPU.Build.0 = Release|Any CPU {DB96C25F-39A9-4A6A-92BC-D1E42717308F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DB96C25F-39A9-4A6A-92BC-D1E42717308F}.Debug|Any CPU.Build.0 = Debug|Any CPU {DB96C25F-39A9-4A6A-92BC-D1E42717308F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB96C25F-39A9-4A6A-92BC-D1E42717308F}.Release|Any CPU.Build.0 = Release|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Release|Any CPU.Build.0 = Release|Any CPU {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}.Release|Any CPU.Build.0 = Release|Any CPU + {FCE88BBD-9BBD-4871-B9B0-DE176D73A6B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCE88BBD-9BBD-4871-B9B0-DE176D73A6B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCE88BBD-9BBD-4871-B9B0-DE176D73A6B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCE88BBD-9BBD-4871-B9B0-DE176D73A6B0}.Release|Any CPU.Build.0 = Release|Any CPU + {EDEF898A-CEFA-4151-8168-D0231A602093}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDEF898A-CEFA-4151-8168-D0231A602093}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDEF898A-CEFA-4151-8168-D0231A602093}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDEF898A-CEFA-4151-8168-D0231A602093}.Release|Any CPU.Build.0 = Release|Any CPU + {D817E3CE-F603-499B-B02A-7DECD017B170}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D817E3CE-F603-499B-B02A-7DECD017B170}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D817E3CE-F603-499B-B02A-7DECD017B170}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D817E3CE-F603-499B-B02A-7DECD017B170}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1534,7 +1543,6 @@ Global {686BF57E-A6FF-467B-AAB3-44DE916A9772} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} {1DDE89EE-5819-441F-A060-2FF4A986F372} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} {655A5B07-39B8-48CD-8590-8AC0C2B708D8} = {8977A560-45C2-4EC2-A849-97335B382C74} - {3D33BBFD-EC63-4E8C-A714-0A48A3809A87} = {BE25E872-1667-4649-9D19-96B83E75A44E} {BFFB5CAE-33B5-447E-9218-BDEBFDA96CB5} = {8977A560-45C2-4EC2-A849-97335B382C74} {FC32EF16-31B1-47B3-B625-A80933CB3F29} = {8977A560-45C2-4EC2-A849-97335B382C74} {453C8E28-81D4-431E-BFB0-F3D413346E51} = {8DBA5174-B0AA-4561-82B1-A46607697753} @@ -1619,11 +1627,14 @@ Global {4D9D7A28-BB44-4F3F-81DA-14F39B853718} = {CC126D03-7EAC-493F-B187-DCDEE1EF6A70} {5BABC440-4F1B-46E8-9068-DD7F02ED25D3} = {3E5FE3DB-45F7-4D83-9097-8F05D3B3AEC6} {5762E483-75CE-4328-A410-511F30737712} = {3E5FE3DB-45F7-4D83-9097-8F05D3B3AEC6} - {47D004BE-F797-430E-8A18-4B0CDFD56643} = {0F3118AE-8D36-4384-8E80-BD6566365305} - {DB96C25F-39A9-4A6A-92BC-D1E42717308F} = {47D004BE-F797-430E-8A18-4B0CDFD56643} + {6D819E80-BA2F-4317-8368-37F8F4434D3A} = {8977A560-45C2-4EC2-A849-97335B382C74} + {DB96C25F-39A9-4A6A-92BC-D1E42717308F} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC} {64EADED3-4B5D-4431-BBE5-A4ABA1C38C00} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350} = {32A48625-F0AD-419D-828B-A50BDABA38EA} + {52ABB0E4-C3A1-4897-B51B-18EDA83F5D20} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC} + {FCE88BBD-9BBD-4871-B9B0-DE176D73A6B0} = {52ABB0E4-C3A1-4897-B51B-18EDA83F5D20} + {EDEF898A-CEFA-4151-8168-D0231A602093} = {52ABB0E4-C3A1-4897-B51B-18EDA83F5D20} + {D817E3CE-F603-499B-B02A-7DECD017B170} = {52ABB0E4-C3A1-4897-B51B-18EDA83F5D20} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} @@ -1668,7 +1679,6 @@ Global src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{64eaded3-4b5d-4431-bbe5-a4aba1c38c00}*SharedItemsImports = 13 src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{686bf57e-a6ff-467b-aab3-44de916a9772}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{699fea05-aea7-403d-827e-53cf4e826955}*SharedItemsImports = 13 - src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{730cadba-701f-4722-9b6f-1fcc0df2c95d}*SharedItemsImports = 5 src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{76242a2d-2600-49dd-8c15-fea07ecb1843}*SharedItemsImports = 5 src\Analyzers\Core\Analyzers\Analyzers.projitems*{76e96966-4780-4040-8197-bde2879516f4}*SharedItemsImports = 13 src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{7b7f4153-ae93-4908-b8f0-430871589f83}*SharedItemsImports = 13 diff --git a/azure-pipelines-integration-corehost.yml b/azure-pipelines-integration-corehost.yml index 710a212c7e06c..aad0cffd853e9 100644 --- a/azure-pipelines-integration-corehost.yml +++ b/azure-pipelines-integration-corehost.yml @@ -4,17 +4,11 @@ trigger: branches: include: - - main - - main-vs-deps - - release/* - - features/* - - demos/* - exclude: - # Since the version of VS on the integration VM images are a moving target, - # we are unable to reliably run integration tests on servicing branches. - - release/dev17.0-vs-deps - - release/dev17.2 - - release/dev17.3 + - release/dev17.4 + - release/dev17.6 + - release/dev17.8 + - release/dev17.9 + - release/dev17.10 # Branches that are allowed to trigger a build via /azp run. # Automatic building of all PRs is disabled in the pipeline's trigger page. @@ -22,17 +16,11 @@ trigger: pr: branches: include: - - main - - main-vs-deps - - release/* - - features/* - - demos/* - exclude: - # Since the version of VS on the integration VM images are a moving target, - # we are unable to reliably run integration tests on servicing branches. - - release/dev17.0-vs-deps - - release/dev17.2 - - release/dev17.3 + - release/dev17.4 + - release/dev17.6 + - release/dev17.8 + - release/dev17.9 + - release/dev17.10 paths: exclude: - docs/* @@ -85,7 +73,6 @@ stages: configuration: Debug testRuns: - oop64bit: true - oopCoreClr: true lspEditor: false runName: VS_Integration_CoreHost_Debug @@ -98,6 +85,5 @@ stages: configuration: Release testRuns: - oop64bit: true - oopCoreClr: true lspEditor: false runName: VS_Integration_CoreHost_Release diff --git a/azure-pipelines-integration-lsp.yml b/azure-pipelines-integration-lsp.yml index 1176304615af8..2fc29141eb936 100644 --- a/azure-pipelines-integration-lsp.yml +++ b/azure-pipelines-integration-lsp.yml @@ -71,6 +71,5 @@ stages: configuration: Debug testRuns: - oop64bit: true - oopCoreClr: false lspEditor: true runName: VS_Integration_LSP_Debug_64 diff --git a/azure-pipelines-integration-scouting.yml b/azure-pipelines-integration-scouting.yml index edddff358055f..346f4dbfe0d38 100644 --- a/azure-pipelines-integration-scouting.yml +++ b/azure-pipelines-integration-scouting.yml @@ -48,11 +48,9 @@ stages: configuration: Debug testRuns: - oop64bit: false - oopCoreClr: false lspEditor: false runName: VS_Integration_Debug_32 - oop64bit: true - oopCoreClr: false lspEditor: false runName: VS_Integration_Debug_64 @@ -64,10 +62,8 @@ stages: configuration: Release testRuns: - oop64bit: false - oopCoreClr: false lspEditor: false runName: VS_Integration_Release_32 - oop64bit: true - oopCoreClr: false lspEditor: false runName: VS_Integration_Release_64 diff --git a/azure-pipelines-integration.yml b/azure-pipelines-integration.yml index 8e9e05ea61b22..b3945c448b11b 100644 --- a/azure-pipelines-integration.yml +++ b/azure-pipelines-integration.yml @@ -83,11 +83,9 @@ stages: testRuns: - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - oop64bit: false - oopCoreClr: false lspEditor: false runName: VS_Integration_Debug_32 - oop64bit: true - oopCoreClr: false lspEditor: false runName: VS_Integration_Debug_64 @@ -99,11 +97,9 @@ stages: configuration: Release testRuns: - oop64bit: false - oopCoreClr: false lspEditor: false runName: VS_Integration_Release_32 - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - oop64bit: true - oopCoreClr: false lspEditor: false runName: VS_Integration_Release_64 diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index 3b713dea8d779..72fb4cfb5b958 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -45,7 +45,7 @@ variables: value: .NETCoreValidation - group: DotNet-Roslyn-SDLValidation-Params - name: Codeql.Enabled - value: true​ + value: true # To retrieve OptProf data we need to authenticate to the VS drop storage. # Get access token with $dn-bot-devdiv-drop-rw-code-rw and dn-bot-dnceng-build-rw-code-rw from DotNet-VSTS-Infra-Access @@ -85,7 +85,7 @@ extends: sdl: sourceAnalysisPool: name: NetCore1ESPool-Svc-Internal - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows sbom: enabled: false @@ -149,6 +149,7 @@ extends: dropFolder: 'artifacts\VSSetup\$(BuildConfiguration)\Insertion' dropName: $(VisualStudio.DropName) accessToken: $(_DevDivDropAccessToken) + dropRetentionDays: 90 # Publish insertion packages to CoreXT store. - output: nuget diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 08aa4304783ab..7ea4ad03c07e3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -55,9 +55,9 @@ variables: - name: UbuntuQueueName ${{ if eq(variables['System.TeamProject'], 'public') }}: - value: Build.Ubuntu.1804.Amd64.Open + value: Build.Ubuntu.2004.Amd64.Open ${{ else }}: - value: Build.Ubuntu.1804.Amd64 + value: Build.Ubuntu.2004.Amd64 - name: WindowsQueueName ${{ if eq(variables['System.TeamProject'], 'public') }}: @@ -79,9 +79,9 @@ variables: - name: HelixUbuntuQueueName ${{ if eq(variables['System.TeamProject'], 'public') }}: - value: Ubuntu.1804.Amd64.Open + value: Ubuntu.2004.Amd64.Open ${{ else }}: - value: Ubuntu.1804.Amd64 + value: Ubuntu.2004.Amd64 - name: HelixMacOsQueueName ${{ if eq(variables['System.TeamProject'], 'public') }}: @@ -148,15 +148,15 @@ stages: # Like template `eng/common/templates/jobs/source-build.yml` - job: Source_Build_Managed displayName: Source-Build (Managed) - container: mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream8 + container: mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream9 pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] - demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open + demands: ImageOverride -equals Build.Ubuntu.2204.Amd64.Open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] - demands: ImageOverride -equals Build.Ubuntu.1804.Amd64 + demands: ImageOverride -equals Build.Ubuntu.2204.Amd64 workspace: clean: all steps: @@ -164,7 +164,7 @@ stages: parameters: platform: name: 'Managed' - container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream8' + container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream9' - stage: Windows_Debug_Desktop dependsOn: Windows_Debug_Build @@ -423,7 +423,7 @@ stages: steps: - template: eng/pipelines/checkout-windows-task.yml - - powershell: eng/make-bootstrap.ps1 -output $(bootstrapDir) + - powershell: eng/make-bootstrap.ps1 -output $(bootstrapDir) -ci displayName: Build Bootstrap Compiler - powershell: eng/test-determinism.ps1 -configuration Debug -bootstrapDir $(bootstrapDir) -ci diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 4fbf571e12e5b..47fb6533a7218 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -10,15 +10,17 @@ efforts behind them. | Feature | Branch | State | Developer | Reviewer | IDE Buddy | LDM Champ | | ------- | ------ | ----- | --------- | -------- | --------- | --------- | -| [Ref Struct Interfaces](https://github.com/dotnet/csharplang/issues/7608) | [RefStructInterfaces](https://github.com/dotnet/roslyn/tree/features/RefStructInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/72124) | [AlekseyTs](https://github.com/AlekseyTs) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | | [agocke](https://github.com/agocke), [jaredpar](https://github.com/jaredpar) | -| [Semi-auto-properties](https://github.com/dotnet/csharplang/issues/140) | [semi-auto-props](https://github.com/dotnet/roslyn/tree/features/semi-auto-props) | [In Progress](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | +| [Partial properties](https://github.com/dotnet/csharplang/issues/6420) | [partial-properties](https://github.com/dotnet/roslyn/tree/features/partial-properties) | [In Progress](https://github.com/dotnet/roslyn/issues/73090) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [Cosifne](https://github.com/Cosifne) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | +| Ref/unsafe in iterators/async | [RefInAsync](https://github.com/dotnet/roslyn/tree/features/RefInAsync) | [In Progress](https://github.com/dotnet/roslyn/issues/72662) | [jjonescz](https://github.com/jjonescz) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | (no IDE impact) | | +| [Ref Struct Interfaces](https://github.com/dotnet/csharplang/issues/7608) | [RefStructInterfaces](https://github.com/dotnet/roslyn/tree/features/RefStructInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/72124) | [AlekseyTs](https://github.com/AlekseyTs) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | [ToddGrun](https://github.com/ToddGrun) | [agocke](https://github.com/agocke), [jaredpar](https://github.com/jaredpar) | +| [Semi-auto-properties](https://github.com/dotnet/csharplang/issues/140) | [semi-auto-props](https://github.com/dotnet/roslyn/tree/features/semi-auto-props) | [In Progress](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | | [Default in deconstruction](https://github.com/dotnet/roslyn/pull/25562) | [decon-default](https://github.com/dotnet/roslyn/tree/features/decon-default) | [In Progress](https://github.com/dotnet/roslyn/issues/25559) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | | [jcouv](https://github.com/jcouv) | -| [Roles/Extensions](https://github.com/dotnet/csharplang/issues/5497) | [roles](https://github.com/dotnet/roslyn/tree/features/roles) | [In Progress](https://github.com/dotnet/roslyn/issues/66722) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [jjonescz](https://github.com/jjonescz) | | [MadsTorgersen](https://github.com/MadsTorgersen) | -| [Escape character](https://github.com/dotnet/csharplang/issues/7400) | N/A | [Merged into 17.9p1](https://github.com/dotnet/roslyn/pull/70497) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), [RikkiGibson](https://github.com/RikkiGibson) | | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | -| [Method group natural type improvements](https://github.com/dotnet/csharplang/blob/main/proposals/method-group-natural-type-improvements.md) | main | [Merged into 17.9p2](https://github.com/dotnet/roslyn/issues/69432) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | | [jcouv](https://github.com/jcouv) | -| [`Lock` object](https://github.com/dotnet/csharplang/issues/7104) | [LockObject](https://github.com/dotnet/roslyn/tree/features/LockObject) | [Merged into 17.10p2](https://github.com/dotnet/roslyn/issues/71888) | [jjonescz](https://github.com/jjonescz) | [cston](https://github.com/cston), [RikkiGibson](https://github.com/RikkiGibson) | | [stephentoub](https://github.com/stephentoub) | -| Implicit indexer access in object initializers | main | [Merged into 17.9p3](https://github.com/dotnet/roslyn/pull/70649) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | | | -| [Params-collections](https://github.com/dotnet/csharplang/issues/7700) | main | [Merged to 17.10p3](https://github.com/dotnet/roslyn/issues/71137) | [AlekseyTs](https://github.com/AlekseyTs) | [RikkiGibson](https://github.com/RikkiGibson), [333fred](https://github.com/333fred) | | [MadsTorgersen](https://github.com/MadsTorgersen), [AlekseyTs](https://github.com/AlekseyTs) | +| [Roles/Extensions](https://github.com/dotnet/csharplang/issues/5497) | [roles](https://github.com/dotnet/roslyn/tree/features/roles) | [In Progress](https://github.com/dotnet/roslyn/issues/66722) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [jjonescz](https://github.com/jjonescz) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [MadsTorgersen](https://github.com/MadsTorgersen) | +| [Escape character](https://github.com/dotnet/csharplang/issues/7400) | N/A | [Merged into 17.9p1](https://github.com/dotnet/roslyn/pull/70497) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), [RikkiGibson](https://github.com/RikkiGibson) | (no IDE impact) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | +| [Method group natural type improvements](https://github.com/dotnet/csharplang/blob/main/proposals/method-group-natural-type-improvements.md) | main | [Merged into 17.9p2](https://github.com/dotnet/roslyn/issues/69432) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | (no IDE impact) | [jcouv](https://github.com/jcouv) | +| [`Lock` object](https://github.com/dotnet/csharplang/issues/7104) | [LockObject](https://github.com/dotnet/roslyn/tree/features/LockObject) | [Merged into 17.10p2](https://github.com/dotnet/roslyn/issues/71888) | [jjonescz](https://github.com/jjonescz) | [cston](https://github.com/cston), [RikkiGibson](https://github.com/RikkiGibson) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) (needs IDE fixer) | [stephentoub](https://github.com/stephentoub) | +| Implicit indexer access in object initializers | main | [Merged into 17.9p3](https://github.com/dotnet/roslyn/pull/70649) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | (no IDE impact) | | +| [Params-collections](https://github.com/dotnet/csharplang/issues/7700) | main | [Merged to 17.10p3](https://github.com/dotnet/roslyn/issues/71137) | [AlekseyTs](https://github.com/AlekseyTs) | [RikkiGibson](https://github.com/RikkiGibson), [333fred](https://github.com/333fred) | [akhera99](https://github.com/akhera99) (needs IDE fixer) | [MadsTorgersen](https://github.com/MadsTorgersen), [AlekseyTs](https://github.com/AlekseyTs) | # C# 12.0 diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md index 21a6b0e2e4cb1..351e636ac80db 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md @@ -28,6 +28,34 @@ public class C } ``` +## Collection expression for type implementing `IEnumerable` must have elements implicitly convertible to `object` + +***Introduced in Visual Studio 2022 version 17.10*** + +*Conversion* of a collection expression to a `struct` or `class` that implements `System.Collections.IEnumerable` and *does not* have a strongly-typed `GetEnumerator()` +requires the elements in the collection expression are implicitly convertible to the `object`. +Previously, the elements of a collection expression targeting an `IEnumerable` implementation were assumed to be convertible to `object`, and converted only when binding to the applicable `Add` method. + +This additional requirement means that collection expression conversions to `IEnumerable` implementations are treated consistently with other target types where the elements in the collection expression must be implicitly convertible to the *iteration type* of the target type. + +This change affects collection expressions targeting `IEnumerable` implementations where the elements rely on target-typing to a strongly-typed `Add` method parameter type. +In the example below, an error is reported that `_ => { }` cannot be implicitly converted to `object`. +```csharp +class Actions : IEnumerable +{ + public void Add(Action action); + // ... +} + +Actions a = [_ => { }]; // error CS8917: The delegate type could not be inferred. +``` + +To resolve the error, the element expression could be explicitly typed. +```csharp +a = [(int _) => { }]; // ok +a = [(Action)(_ => { })]; // ok +``` + ## Collection expression target type must have constructor and `Add` method ***Introduced in Visual Studio 2022 version 17.10*** @@ -35,7 +63,7 @@ public class C *Conversion* of a collection expression to a `struct` or `class` that implements `System.Collections.IEnumerable` and *does not* have a `CollectionBuilderAttribute` requires the target type to have an accessible constructor that can be called with no arguments and, if the collection expression is not empty, the target type must have an accessible `Add` method -that can be called with a single argument of [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/statements.md#1395-the-foreach-statement) of the target type. +that can be called with a single argument. Previously, the constructor and `Add` methods were required for *construction* of the collection instance but not for *conversion*. That meant the following call was ambiguous since both `char[]` and `string` were valid target types for the collection expression. @@ -47,24 +75,6 @@ static void Print(char[] arg) { } static void Print(string arg) { } ``` -Previously, the collection expression in `y = [1, 2, 3]` was allowed since construction only requires an applicable `Add` method for each element expression. -The collection expression is now an error because of the conversion requirement for an `Add` method than be called with an argument of the iteration type `object`. -```csharp -// ok: Add is not required for empty collection -MyCollection x = []; - -// error CS9215: Collection expression type must have an applicable instance or extension method 'Add' -// that can be called with an argument of iteration type 'object'. -// The best overloaded method is 'MyCollection.Add(int)'. -MyCollection y = [1, 2, 3]; - -class MyCollection : IEnumerable -{ - public void Add(int i) { ... } - IEnumerator IEnumerable.GetEnumerator() { ... } -} -``` - ## `ref` arguments can be passed to `in` parameters ***Introduced in Visual Studio 2022 version 17.8p2*** diff --git a/docs/compilers/CSharp/Deviations from Standard.md b/docs/compilers/CSharp/Deviations from Standard.md index f5a1e61873048..b7f6077d32e77 100644 --- a/docs/compilers/CSharp/Deviations from Standard.md +++ b/docs/compilers/CSharp/Deviations from Standard.md @@ -35,3 +35,38 @@ class EnumConversionTest ``` Conversions are (correctly) *not* permitted from constant expressions which have a type of `bool`, other enumerations, or reference types. + +# Member lookup + +From [§12.5.1](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#125-member-lookup): + +> - Finally, having removed hidden members, the result of the lookup is determined: +> - If the set consists of a single member that is not a method, then this member is the result of the lookup. +> - Otherwise, if the set contains only methods, then this group of methods is the result of the lookup. +> - Otherwise, the lookup is ambiguous, and a binding-time error occurs. + +Roslyn instead implements a preference for methods over non-method symbols: + +```csharp +var x = I.M; // binds to I1.M (method) +x(); + +System.Action y = I.M; // binds to I1.M (method) + +interface I1 { static void M() { } } +interface I2 { static int M => 0; } +interface I3 { static int M = 0; } +interface I : I1, I2, I3 { } +``` + +```csharp +I i = null; +var x = i.M; // binds to I1.M (method) +x(); + +System.Action y = i.M; // binds to I1.M (method) + +interface I1 { void M() { } } +interface I2 { int M => 0; } +interface I : I1, I2 { } +``` diff --git a/docs/compilers/CSharp/Warnversion Warning Waves.md b/docs/compilers/CSharp/Warnversion Warning Waves.md index fc070915f27bf..f80c80e658838 100644 --- a/docs/compilers/CSharp/Warnversion Warning Waves.md +++ b/docs/compilers/CSharp/Warnversion Warning Waves.md @@ -19,7 +19,7 @@ The compiler shipped with .NET 9 (the C# 13 compiler) contains the following war | Warning ID | Description | |------------|-------------| -| CS9230 | ['yield return' should not be used in the body of a lock statement](https://github.com/dotnet/roslyn/issues/72443) | +| CS9237 | ['yield return' should not be used in the body of a lock statement](https://github.com/dotnet/roslyn/issues/72443) | ## Warning level 8 diff --git a/docs/contributing/Bootstrap Builds.md b/docs/contributing/Bootstrap Builds.md index 52e1a638cc877..128a4d59fe740 100644 --- a/docs/contributing/Bootstrap Builds.md +++ b/docs/contributing/Bootstrap Builds.md @@ -104,5 +104,5 @@ https://github.com/dotnet/roslyn/blob/d73d31cbccb9aa850f3582afb464b709fef88fd7/s Next just run the bootstrap build locally, wait for the `Debug.Assert` to trigger which pops up a dialog. From there you can attach to the VBCSCompiler process and debug through the problem ```cmd -> Build.cmd -bootstrap -bootstrapConfiguration Debug +> Build.cmd -bootstrap ``` diff --git a/docs/features/incremental-generators.cookbook.md b/docs/features/incremental-generators.cookbook.md index 9bbb0b9134c9d..3cf0870f6bf57 100644 --- a/docs/features/incremental-generators.cookbook.md +++ b/docs/features/incremental-generators.cookbook.md @@ -191,7 +191,7 @@ public class FileTransformGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { var pipeline = context.AdditionalTextsProvider - .Where(static (text, cancellationToken) => text.Path.EndsWith(".xml")) + .Where(static (text) => text.Path.EndsWith(".xml")) .Select(static (text, cancellationToken) => { var name = Path.GetFileName(text.Path); diff --git a/docs/features/interceptors.md b/docs/features/interceptors.md index b96fe71e646b6..f93209462b994 100644 --- a/docs/features/interceptors.md +++ b/docs/features/interceptors.md @@ -12,9 +12,9 @@ using System; using System.Runtime.CompilerServices; var c = new C(); -c.InterceptableMethod(1); // (L1,C1): prints "interceptor 1" -c.InterceptableMethod(1); // (L2,C2): prints "other interceptor 1" -c.InterceptableMethod(2); // (L3,C3): prints "other interceptor 2" +c.InterceptableMethod(1); // L1: prints "interceptor 1" +c.InterceptableMethod(1); // L2: prints "other interceptor 1" +c.InterceptableMethod(2); // L3: prints "other interceptor 2" c.InterceptableMethod(1); // prints "interceptable 1" class C @@ -28,14 +28,14 @@ class C // generated code static class D { - [InterceptsLocation("Program.cs", line: /*L1*/, character: /*C1*/)] // refers to the call at (L1, C1) + [InterceptsLocation(version: 1, data: "...(refers to the call at L1)")] public static void InterceptorMethod(this C c, int param) { Console.WriteLine($"interceptor {param}"); } - [InterceptsLocation("Program.cs", line: /*L2*/, character: /*C2*/)] // refers to the call at (L2, C2) - [InterceptsLocation("Program.cs", line: /*L3*/, character: /*C3*/)] // refers to the call at (L3, C3) + [InterceptsLocation(version: 1, data: "...(refers to the call at L2)")] + [InterceptsLocation(version: 1, data: "...(refers to the call at L3)")] public static void OtherInterceptorMethod(this C c, int param) { Console.WriteLine($"other interceptor {param}"); @@ -54,7 +54,7 @@ A method indicates that it is an *interceptor* by adding one or more `[Intercept namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute + public sealed class InterceptsLocationAttribute(int version, string data) : Attribute { } } @@ -62,33 +62,32 @@ namespace System.Runtime.CompilerServices Any "ordinary method" (i.e. with `MethodKind.Ordinary`) can have its calls intercepted. +In addition to "ordinary" forms `M()` and `receiver.M()`, a call within a conditional access, e.g. of the form `receiver?.M()` can be intercepted. A call whose receiver is a pointer member access, e.g. of the form `ptr->M()`, can also be intercepted. + `[InterceptsLocation]` attributes included in source are emitted to the resulting assembly, just like other custom attributes. File-local declarations of this type (`file class InterceptsLocationAttribute`) are valid and usages are recognized by the compiler when they are within the same file and compilation. A generator which needs to declare this attribute should use a file-local declaration to ensure it doesn't conflict with other generators that need to do the same thing. -#### File paths - -The *referenced syntax tree* of an `[InterceptsLocation]` is determined by normalizing the `filePath` argument value relative to the path of the containing syntax tree of the `[InterceptsLocation]` usage, similar to how paths in `#line` directives are normalized. Let this normalized path be called `normalizedInterceptorPath`. If exactly one syntax tree in the compilation has a normalized path which matches `normalizedInterceptorPath` by ordinal string comparison, that is the *referenced syntax tree*. Otherwise, an error occurs. +In prior experimental releases of the feature, a well-known constructor signature `InterceptsLocation(string path, int line, int column)]` was also supported. Support for this constructor will be **dropped** prior to stable release of the feature. -`#line` directives are not considered when determining the call referenced by an `[InterceptsLocation]` attribute. In other words, the file path, line and column numbers used in `[InterceptsLocation]` are expected to refer to *unmapped* source locations. +#### Location encoding -Temporarily, for compatibility purposes, when the initial matching strategy outlined above fails to match any syntax trees, we will fall back to a "compat" matching strategy which works in the following way: -- A *mapped path* of each syntax tree is determined by applying [`/pathmap`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.commandlinearguments.pathmap?view=roslyn-dotnet-4.7.0) substitution to `SyntaxTree.FilePath`. -- For a given `[InterceptsLocation]` usage, the `filePath` argument value is compared to the *mapped path* of each syntax tree using ordinal string comparison. If exactly one syntax tree matches under this comparison, that is the *referenced syntax tree*. Otherwise, an error occurs. +The arguments to `[InterceptsLocation]` are: +1. a version number. The compiler may introduce new encodings for the location in the future, with corresponding new version numbers. +2. an opaque data string. This is not intended to be human-readable. -Support for the "compat" strategy will be dropped prior to stable release. Tracked by https://github.com/dotnet/roslyn/issues/72265. +The "version 1" data encoding is a base64-encoded string consisting of the following data: +- 16 byte xxHash128 content checksum of the file containing the intercepted call. +- int32 in little-endian format for the position (i.e. `SyntaxNode.Position`) of the call in syntax. +- utf-8 string data containing a display file name, used for error reporting. #### Position -Line and column numbers in `[InterceptsLocation]` are 1-indexed to match existing places where source locations are displayed to the user. For example, in `Diagnostic.ToString`. - The location of the call is the location of the simple name syntax which denotes the interceptable method. For example, in `app.MapGet(...)`, the name syntax for `MapGet` would be considered the location of the call. For a static method call like `System.Console.WriteLine(...)`, the name syntax for `WriteLine` is the location of the call. If we allow intercepting calls to property accessors in the future (e.g `obj.Property`), we would also be able to use the name syntax in this way. #### Attribute creation -The goal of the above decisions is to make it so that when source generators are filling in `[InterceptsLocation(...)]`, they simply need to read `nameSyntax.SyntaxTree.FilePath` and `nameSyntax.GetLineSpan().Span.Start` for the exact file path and position information they need to use. - -We should provide samples of recommended coding patterns for generator authors to show correct usage of these, including the "translation" from 0-indexed to 1-indexed positions. +Roslyn provides an API `GetInterceptableLocation(this SemanticModel, InvocationExpressionSyntax, CancellationToken)` for inserting `[InterceptsLocation]` into generated source code. We recommend that source generators depend on this API in order to intercept calls. See https://github.com/dotnet/roslyn/issues/72133 for further details. ### Non-invocation method usages @@ -103,7 +102,7 @@ Interceptors cannot be declared in generic types at any level of nesting. Interceptors must either be non-generic, or have arity equal to the sum of the arity of the original method's arity and containing type arities. For example: ```cs -Grandparent.Parent.Original(1, false, "a"); +Grandparent.Parent.Original(1, false, "a"); // L1 class Grandparent { @@ -115,7 +114,7 @@ class Grandparent class Interceptors { - [InterceptsLocation("Program.cs", 1, 33)] + [InterceptsLocation(1, "..(refers to call at L1)")] public static void Interceptor(T1 t1, T2 t2, T3 t3) { } } ``` @@ -136,13 +135,13 @@ static class Program { public static void M(T2 t) { - C.InterceptableMethod(t); + C.InterceptableMethod(t); // L1 } } static class D { - [InterceptsLocation("Program.cs", 12, 11)] + [InterceptsLocation(1, "..(refers to call at L1)")] public static void Interceptor1(T2 t) => throw null!; } ``` @@ -184,6 +183,8 @@ We may also want to consider adjusting behavior of `[EditorBrowsable]` to work i Interceptors are treated like a post-compilation step in this design. Diagnostics are given for misuse of interceptors, but some diagnostics are only given in the command-line build and not in the IDE. There is limited traceability in the editor for which calls in a compilation are actually being intercepted. If this feature is brought forward past the experimental stage, this limitation will need to be re-examined. +There is an experimental public API `GetInterceptorMethod(this SemanticModel, InvocationExpressionSyntax, CancellationToken)` which enables analyzers to determine if a call is being intercepted, and if so, which method is intercepting the call. See https://github.com/dotnet/roslyn/issues/72093 for further details. + ### User opt-in To use interceptors, the user project must specify the property ``. This is a list of namespaces which are allowed to contain interceptors. diff --git a/docs/wiki/Troubleshooting-tips.md b/docs/wiki/Troubleshooting-tips.md index 614ae88a5d9ae..59d3509f7ec81 100644 --- a/docs/wiki/Troubleshooting-tips.md +++ b/docs/wiki/Troubleshooting-tips.md @@ -1,6 +1,6 @@ # Capturing a crash dump -## Using a registry setting +## Using a registry setting (recommended on Windows) Create a registry key file (`dump.reg`) with the contents below, then execute it. The settings mean that every crash will produce a full dump (`DumpType`=2) in the folder specified by `DumpFolder`, and at most one will be kept (every subsequent crash will overwrite the file, because `DumpCount`=1). @@ -19,6 +19,10 @@ Windows Registry Editor Version 5.00 More [information](https://msdn.microsoft.com/en-us/library/windows/desktop/bb787181(v=vs.85).aspx) +## Using environment variables (recommended on Linux) + +Define the container with the [correct variables](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/collect-dumps-crash) to collect a dump on crash. + # Running the compiler with a long command line Often times the command-line recorded by msbuild logs is very long. Simply copy/pasting it into a command window fails, because the line gets truncated. diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index ea4b42a8e1785..8adaa8740c2ac 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -7,7 +7,7 @@ 0.1.187-beta 4.8.0-3.final - 17.9.80-preview + 17.10.72-preview 6.0.0-rtm.21518.12 6.0.0-rtm.21518.12 7.0.0-alpha.1.22060.1 @@ -22,6 +22,7 @@ 2.4.1 2.1.0 17.10.22-preview-1 + 17.10.72-preview - - + + @@ -292,13 +293,13 @@ - - - - + + + + - - + + - + https://github.com/dotnet/source-build-externals - b46b7e6859f4094cd7f3e00dc0471d62f5d8d051 + f4e750201aaf6bad67391a52e8138bbd7abe3019 - + https://github.com/dotnet/source-build-reference-packages - 8d6e9cf10f64ff8fc02e434b516f6ca87c4b7215 + c0b5d69a1a1513528c77fffff708c7502d57c35c - + https://github.com/dotnet/command-line-api - 5ea97af07263ea3ef68a18557c8aa3f7e3200bda + 963d34b1fb712c673bfb198133d7e988182c9ef4 - + https://github.com/dotnet/command-line-api - 5ea97af07263ea3ef68a18557c8aa3f7e3200bda + 963d34b1fb712c673bfb198133d7e988182c9ef4 @@ -110,14 +110,14 @@ - + https://github.com/dotnet/arcade - 5c3fdd3b5aaaa32b24383ec12a60b37ebff13079 + 188340e12c0a372b1681ad6a5e72c608021efdba - + https://github.com/dotnet/arcade - 5c3fdd3b5aaaa32b24383ec12a60b37ebff13079 + 188340e12c0a372b1681ad6a5e72c608021efdba @@ -144,9 +144,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 5c3fdd3b5aaaa32b24383ec12a60b37ebff13079 + 188340e12c0a372b1681ad6a5e72c608021efdba https://github.com/dotnet/roslyn-analyzers diff --git a/eng/Versions.props b/eng/Versions.props index ca2c67bff31aa..46a3bd50e84f1 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -6,9 +6,9 @@ --> 4 - 10 + 11 0 - 3 + 2 $(MajorVersion).$(MinorVersion).$(PatchVersion) - 2.0.0-beta4.24126.1 + 2.0.0-beta4.24209.3 8.0.0 8.0.0 8.0.0 diff --git a/eng/build.ps1 b/eng/build.ps1 index d49c55719bcd5..812d8cc8a242f 100644 --- a/eng/build.ps1 +++ b/eng/build.ps1 @@ -45,7 +45,6 @@ param ( [switch]$warnAsError = $false, [switch]$sourceBuild = $false, [switch]$oop64bit = $true, - [switch]$oopCoreClr = $false, [switch]$lspEditor = $false, # official build settings @@ -350,6 +349,7 @@ function GetCompilerTestAssembliesIncludePaths() { $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.Semantic\.UnitTests$'" $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.Emit\.UnitTests$'" $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.Emit2\.UnitTests$'" + $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.Emit3\.UnitTests$'" $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.IOperation\.UnitTests$'" $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.CommandLine\.UnitTests$'" $assemblies += " --include '^Microsoft\.CodeAnalysis\.VisualBasic\.Syntax\.UnitTests$'" @@ -612,6 +612,9 @@ function Deploy-VsixViaTool() { # Disable text spell checker to avoid spurious warnings in the error list &$vsRegEdit set "$vsDir" $hive HKCU "FeatureFlags\Editor\EnableSpellChecker" Value dword 0 + # Run source generators automatically during integration tests. + &$vsRegEdit set "$vsDir" $hive HKCU "FeatureFlags\Roslyn\SourceGeneratorExecutionBalanced" Value dword 0 + # Configure LSP $lspRegistryValue = [int]$lspEditor.ToBool() &$vsRegEdit set "$vsDir" $hive HKCU "FeatureFlags\Roslyn\LSP\Editor" Value dword $lspRegistryValue @@ -624,10 +627,6 @@ function Deploy-VsixViaTool() { # Configure RemoteHostOptions.OOP64Bit for testing $oop64bitValue = [int]$oop64bit.ToBool() &$vsRegEdit set "$vsDir" $hive HKCU "Roslyn\Internal\OnOff\Features" OOP64Bit dword $oop64bitValue - - # Configure RemoteHostOptions.OOPCoreClr for testing - $oopCoreClrValue = [int]$oopCoreClr.ToBool() - &$vsRegEdit set "$vsDir" $hive HKCU "Roslyn\Internal\OnOff\Features" OOPCoreClr dword $oopCoreClrValue } # Ensure that procdump is available on the machine. Returns the path to the directory that contains @@ -696,7 +695,6 @@ function Setup-IntegrationTestRun() { } $env:ROSLYN_OOP64BIT = "$oop64bit" - $env:ROSLYN_OOPCORECLR = "$oopCoreClr" $env:ROSLYN_LSPEDITOR = "$lspEditor" } diff --git a/eng/build.sh b/eng/build.sh index 44a7f987d853a..5a8558e054d66 100755 --- a/eng/build.sh +++ b/eng/build.sh @@ -318,6 +318,7 @@ function GetCompilerTestAssembliesIncludePaths { assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.Semantic\.UnitTests$'" assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.Emit\.UnitTests$'" assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.Emit2\.UnitTests$'" + assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.Emit3\.UnitTests$'" assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.IOperation\.UnitTests$'" assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.CommandLine\.UnitTests$'" assemblies+=" --include '^Microsoft\.CodeAnalysis\.VisualBasic\.Syntax\.UnitTests$'" diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh index f5c1ec7eafeb2..2d5660642b8d4 100644 --- a/eng/common/native/init-compiler.sh +++ b/eng/common/native/init-compiler.sh @@ -63,7 +63,7 @@ if [ -z "$CLR_CC" ]; then # Set default versions if [ -z "$majorVersion" ]; then # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. - if [ "$compiler" = "clang" ]; then versions="17 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5" + if [ "$compiler" = "clang" ]; then versions="18 17 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5" elif [ "$compiler" = "gcc" ]; then versions="13 12 11 10 9 8 7 6 5 4.9"; fi for version in $versions; do diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml index 647e3f92e5f37..1f035fee73f4a 100644 --- a/eng/common/templates-official/job/job.yml +++ b/eng/common/templates-official/job/job.yml @@ -128,7 +128,7 @@ jobs: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - - task: MicroBuildSigningPlugin@3 + - task: MicroBuildSigningPlugin@4 displayName: Install MicroBuild plugin inputs: signType: $(_SignType) @@ -136,6 +136,7 @@ jobs: feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json env: TeamName: $(_TeamName) + MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' continueOnError: ${{ parameters.continueOnError }} condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) @@ -206,9 +207,11 @@ jobs: continueOnError: true condition: always() - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: - - publish: artifacts/log - artifact: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} - displayName: Publish logs + - task: 1ES.PublishPipelineArtifact@1 + inputs: + targetPath: 'artifacts/log' + artifactName: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} + displayName: 'Publish logs' continueOnError: true condition: always() @@ -253,7 +256,9 @@ jobs: IgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} - ${{ if eq(parameters.enableBuildRetry, 'true') }}: - - publish: $(Build.SourcesDirectory)\eng\common\BuildConfiguration - artifact: BuildConfiguration - displayName: Publish build retry configuration - continueOnError: true + - task: 1ES.PublishPipelineArtifact@1 + inputs: + targetPath: '$(Build.SourcesDirectory)\eng\common\BuildConfiguration' + artifactName: 'BuildConfiguration' + displayName: 'Publish build retry configuration' + continueOnError: true \ No newline at end of file diff --git a/eng/common/templates-official/job/onelocbuild.yml b/eng/common/templates-official/job/onelocbuild.yml index ba9ba49303292..52b4d05d3f8dd 100644 --- a/eng/common/templates-official/job/onelocbuild.yml +++ b/eng/common/templates-official/job/onelocbuild.yml @@ -56,7 +56,7 @@ jobs: # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: diff --git a/eng/common/templates-official/job/publish-build-assets.yml b/eng/common/templates-official/job/publish-build-assets.yml index ea5104625fac5..589ac80a18b74 100644 --- a/eng/common/templates-official/job/publish-build-assets.yml +++ b/eng/common/templates-official/job/publish-build-assets.yml @@ -60,8 +60,8 @@ jobs: os: windows # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: - name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 os: windows steps: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: @@ -94,14 +94,16 @@ jobs: inputs: targetType: inline script: | - Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value $(BARBuildId) - Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value "$(DefaultChannels)" - Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value $(IsStableBuild) + New-Item -Path "$(Build.StagingDirectory)/ReleaseConfigs" -ItemType Directory -Force + $filePath = "$(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt" + Add-Content -Path $filePath -Value $(BARBuildId) + Add-Content -Path $filePath -Value "$(DefaultChannels)" + Add-Content -Path $filePath -Value $(IsStableBuild) - task: 1ES.PublishBuildArtifacts@1 displayName: Publish ReleaseConfigs Artifact inputs: - PathtoPublish: '$(Build.StagingDirectory)/ReleaseConfigs.txt' + PathtoPublish: '$(Build.StagingDirectory)/ReleaseConfigs' PublishLocation: Container ArtifactName: ReleaseConfigs diff --git a/eng/common/templates-official/job/source-build.yml b/eng/common/templates-official/job/source-build.yml index 8aba3b44bb252..f193dfbe23668 100644 --- a/eng/common/templates-official/job/source-build.yml +++ b/eng/common/templates-official/job/source-build.yml @@ -52,7 +52,7 @@ jobs: ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] - image: 1es-mariner-2-pt + image: 1es-mariner-2 os: linux ${{ if ne(parameters.platform.pool, '') }}: diff --git a/eng/common/templates-official/job/source-index-stage1.yml b/eng/common/templates-official/job/source-index-stage1.yml index 4b63373917085..f0513aee5b0da 100644 --- a/eng/common/templates-official/job/source-index-stage1.yml +++ b/eng/common/templates-official/job/source-index-stage1.yml @@ -33,7 +33,7 @@ jobs: demands: ImageOverride -equals windows.vs2019.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: windows.vs2022.amd64 os: windows steps: diff --git a/eng/common/templates-official/post-build/post-build.yml b/eng/common/templates-official/post-build/post-build.yml index 5c98fe1c0f3a9..da1f40958b450 100644 --- a/eng/common/templates-official/post-build/post-build.yml +++ b/eng/common/templates-official/post-build/post-build.yml @@ -110,7 +110,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: @@ -150,7 +150,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: - template: setup-maestro-vars.yml @@ -208,7 +208,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: - template: setup-maestro-vars.yml @@ -261,8 +261,8 @@ stages: os: windows # If it's not devdiv, it's dnceng ${{ else }}: - name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 os: windows steps: - template: setup-maestro-vars.yml diff --git a/eng/common/templates-official/steps/component-governance.yml b/eng/common/templates-official/steps/component-governance.yml index 0ecec47b0c917..cbba0596709da 100644 --- a/eng/common/templates-official/steps/component-governance.yml +++ b/eng/common/templates-official/steps/component-governance.yml @@ -4,7 +4,7 @@ parameters: steps: - ${{ if eq(parameters.disableComponentGovernance, 'true') }}: - - script: "echo ##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" displayName: Set skipComponentGovernanceDetection variable - ${{ if ne(parameters.disableComponentGovernance, 'true') }}: - task: ComponentGovernanceComponentDetection@0 diff --git a/eng/common/templates-official/variables/pool-providers.yml b/eng/common/templates-official/variables/pool-providers.yml index beab7d1bfba06..1f308b24efc43 100644 --- a/eng/common/templates-official/variables/pool-providers.yml +++ b/eng/common/templates-official/variables/pool-providers.yml @@ -23,7 +23,7 @@ # # pool: # name: $(DncEngInternalBuildPool) -# image: 1es-windows-2022-pt +# image: 1es-windows-2022 variables: # Coalesce the target and source branches so we know when a PR targets a release branch diff --git a/eng/common/templates/steps/component-governance.yml b/eng/common/templates/steps/component-governance.yml index 0ecec47b0c917..cbba0596709da 100644 --- a/eng/common/templates/steps/component-governance.yml +++ b/eng/common/templates/steps/component-governance.yml @@ -4,7 +4,7 @@ parameters: steps: - ${{ if eq(parameters.disableComponentGovernance, 'true') }}: - - script: "echo ##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" displayName: Set skipComponentGovernanceDetection variable - ${{ if ne(parameters.disableComponentGovernance, 'true') }}: - task: ComponentGovernanceComponentDetection@0 diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index b92b2e1dd3e92..7827d75c24bb5 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -79,6 +79,7 @@ "Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.TypeScript": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.UnitTesting": "vs-impl", + "Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.Xamarin.Remote": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.Xaml": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch": "vs-impl", @@ -87,7 +88,8 @@ "Microsoft.CodeAnalysis.Remote.Workspaces": "vs-impl", "Microsoft.VisualStudio.LanguageServices.LiveShare": "vs-impl", "Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient": "vs-impl", - "Microsoft.CommonLanguageServerProtocol.Framework": "vs-impl" + "Microsoft.CommonLanguageServerProtocol.Framework": "vs-impl", + "Microsoft.CommonLanguageServerProtocol.Framework.Binary": "vs-impl" } }, "comment-about-servicing-branches": "For a list of VS versions under servicing, see https://docs.microsoft.com/en-us/visualstudio/releases/2019/servicing#support-options-for-enterprise-and-professional-customers", @@ -203,9 +205,9 @@ "vsBranch": "rel/d17.10", "vsMajorVersion": 17, "insertionCreateDraftPR": false, - "insertionTitlePrefix": "[d17.10 P2]" + "insertionTitlePrefix": "[d17.10]" }, - "main": { + "release/dev17.11": { "nugetKind": [ "Shipping", "NonShipping" @@ -213,9 +215,9 @@ "vsBranch": "main", "vsMajorVersion": 17, "insertionCreateDraftPR": false, - "insertionTitlePrefix": "[d17.10 P3]" + "insertionTitlePrefix": "[d17.11 P1]" }, - "features/vscode_net8": { + "main": { "nugetKind": [ "Shipping", "NonShipping" @@ -223,7 +225,7 @@ "vsBranch": "main", "vsMajorVersion": 17, "insertionCreateDraftPR": true, - "insertionTitlePrefix": "[Validation]" + "insertionTitlePrefix": "[d17.11 P2]" }, "dev/andrha/telemetry": { "nugetKind": [ diff --git a/eng/pipelines/insert.yml b/eng/pipelines/insert.yml index c70c3a6674f10..98c0834d96d70 100644 --- a/eng/pipelines/insert.yml +++ b/eng/pipelines/insert.yml @@ -177,7 +177,7 @@ steps: # Now that everything is set, actually perform the insertion. - powershell: | mv RoslynTools.VisualStudioInsertionTool.* RIT - .\RIT\tools\net46\OneOffInsertion.ps1 ` + .\RIT\tools\net472\OneOffInsertion.ps1 ` -autoComplete "$(Template.AutoComplete)" ` -buildQueueName "$(Build.DefinitionName)" ` -cherryPick "(default)" ` diff --git a/eng/pipelines/test-integration-helix.yml b/eng/pipelines/test-integration-helix.yml index 29b1c1138e4e1..840b34818bb86 100644 --- a/eng/pipelines/test-integration-helix.yml +++ b/eng/pipelines/test-integration-helix.yml @@ -13,7 +13,6 @@ parameters: type: object default: - oop64bit: true - oopCoreClr: false lspEditor: false runName: 64 @@ -88,7 +87,7 @@ stages: displayName: Run Integration Tests inputs: filePath: eng/build.ps1 - arguments: -ci -prepareMachine -testVsi -configuration ${{ parameters.configuration }} -oop64bit:$${{ testParameters.oop64bit }} -oopCoreClr:$${{ testParameters.oopCoreClr }} -collectDumps -lspEditor:$${{ testParameters.lspEditor }} + arguments: -ci -prepareMachine -testVsi -configuration ${{ parameters.configuration }} -oop64bit:$${{ testParameters.oop64bit }} -collectDumps -lspEditor:$${{ testParameters.lspEditor }} # These are temporary publishing steps - once the tests run on helix, the artifacts will be attached to the helix payload. - task: PublishTestResults@2 @@ -97,7 +96,7 @@ stages: testRunner: XUnit testResultsFiles: $(Build.SourcesDirectory)\artifacts\TestResults\${{ parameters.configuration }}\*.xml mergeTestResults: true - testRunTitle: '$(System.JobAttempt)-Integration ${{ parameters.configuration }} OOP64_${{ testParameters.oop64bit }} OOPCoreClr_${{ testParameters.oopCoreClr }}' + testRunTitle: '$(System.JobAttempt)-Integration ${{ parameters.configuration }} OOP64_${{ testParameters.oop64bit }}' condition: always() # Dumps from test timeouts or crashes get published to the test results directory by dotnet test, so make sure to publish any here. @@ -105,7 +104,7 @@ stages: displayName: Publish Test Results Directory inputs: PathtoPublish: '$(Build.SourcesDirectory)\artifacts\TestResults\${{ parameters.configuration }}' - ArtifactName: '$(System.JobAttempt)-Logs ${{ parameters.configuration }} OOP64_${{ testParameters.oop64bit }} OOPCoreClr_${{ testParameters.oopCoreClr }} LspEditor_${{ testParameters.lspEditor }} $(Build.BuildNumber)' + ArtifactName: '$(System.JobAttempt)-Logs ${{ parameters.configuration }} OOP64_${{ testParameters.oop64bit }} LspEditor_${{ testParameters.lspEditor }} $(Build.BuildNumber)' publishLocation: Container continueOnError: true condition: not(succeeded()) @@ -114,7 +113,7 @@ stages: displayName: Publish Logs inputs: PathtoPublish: '$(Build.SourcesDirectory)\artifacts\log\${{ parameters.configuration }}' - ArtifactName: '$(System.JobAttempt)-Logs ${{ parameters.configuration }} OOP64_${{ testParameters.oop64bit }} OOPCoreClr_${{ testParameters.oopCoreClr }} LspEditor_${{ testParameters.lspEditor }} $(Build.BuildNumber)' + ArtifactName: '$(System.JobAttempt)-Logs ${{ parameters.configuration }} OOP64_${{ testParameters.oop64bit }} LspEditor_${{ testParameters.lspEditor }} $(Build.BuildNumber)' publishLocation: Container continueOnError: true condition: not(succeeded()) diff --git a/eng/pipelines/test-integration-job.yml b/eng/pipelines/test-integration-job.yml index 92a4b46c26928..f9ca042c0f87c 100644 --- a/eng/pipelines/test-integration-job.yml +++ b/eng/pipelines/test-integration-job.yml @@ -8,9 +8,6 @@ parameters: # So in order to pass a parameter that comes from a variable these must be typed as string. type: string default: true - - name: oopCoreClr - type: string - default: false - name: lspEditor type: string default: false @@ -27,7 +24,7 @@ steps: displayName: Build and Test inputs: filePath: eng/build.ps1 - arguments: -ci -restore -build -binaryLog -configuration ${{ parameters.configuration }} -prepareMachine -testVsi -oop64bit:$${{ parameters.oop64bit }} -oopCoreClr:$${{ parameters.oopCoreClr }} -collectDumps -lspEditor:$${{ parameters.lspEditor }} + arguments: -ci -restore -build -binaryLog -configuration ${{ parameters.configuration }} -prepareMachine -testVsi -oop64bit:$${{ parameters.oop64bit }} -collectDumps -lspEditor:$${{ parameters.lspEditor }} - task: PublishTestResults@2 displayName: Publish xUnit Test Results @@ -35,7 +32,7 @@ steps: testRunner: XUnit testResultsFiles: $(Build.SourcesDirectory)\artifacts\TestResults\${{ parameters.configuration }}\*.xml mergeTestResults: true - testRunTitle: '$(System.JobAttempt)-Integration ${{ parameters.configuration }} OOP64_${{ parameters.oop64bit }} OOPCoreClr_${{ parameters.oopCoreClr }}' + testRunTitle: '$(System.JobAttempt)-Integration ${{ parameters.configuration }} OOP64_${{ parameters.oop64bit }}' condition: always() # Dumps from test timeouts or crashes get published to the test results directory by dotnet test, so make sure to publish any here. @@ -43,7 +40,7 @@ steps: displayName: Publish Test Results Directory inputs: PathtoPublish: '$(Build.SourcesDirectory)\artifacts\TestResults\${{ parameters.configuration }}' - ArtifactName: '$(System.JobAttempt)-Logs ${{ parameters.configuration }} OOP64_${{ parameters.oop64bit }} OOPCoreClr_${{ parameters.oopCoreClr }} LspEditor_${{ parameters.lspEditor }} $(Build.BuildNumber)' + ArtifactName: '$(System.JobAttempt)-Logs ${{ parameters.configuration }} OOP64_${{ parameters.oop64bit }} LspEditor_${{ parameters.lspEditor }} $(Build.BuildNumber)' publishLocation: Container continueOnError: true condition: not(succeeded()) @@ -52,7 +49,7 @@ steps: displayName: Publish Logs inputs: PathtoPublish: '$(Build.SourcesDirectory)\artifacts\log\${{ parameters.configuration }}' - ArtifactName: '$(System.JobAttempt)-Logs ${{ parameters.configuration }} OOP64_${{ parameters.oop64bit }} OOPCoreClr_${{ parameters.oopCoreClr }} LspEditor_${{ parameters.lspEditor }} $(Build.BuildNumber)' + ArtifactName: '$(System.JobAttempt)-Logs ${{ parameters.configuration }} OOP64_${{ parameters.oop64bit }} LspEditor_${{ parameters.lspEditor }} $(Build.BuildNumber)' publishLocation: Container continueOnError: true condition: not(succeeded()) @@ -61,7 +58,7 @@ steps: displayName: Publish Screenshots and Test Attachments (Old Tests) inputs: PathtoPublish: '$(Build.SourcesDirectory)\artifacts\bin\Microsoft.VisualStudio.LanguageServices.IntegrationTests\${{ parameters.configuration }}\net472\TestResults' - ArtifactName: '$(System.JobAttempt)-Screenshots ${{ parameters.configuration }} OOP64_${{ parameters.oop64bit }} OOPCoreClr_${{ parameters.oopCoreClr }} LspEditor_${{ parameters.lspEditor }} $(Build.BuildNumber)' + ArtifactName: '$(System.JobAttempt)-Screenshots ${{ parameters.configuration }} OOP64_${{ parameters.oop64bit }} LspEditor_${{ parameters.lspEditor }} $(Build.BuildNumber)' publishLocation: Container continueOnError: true condition: not(succeeded()) diff --git a/eng/targets/GeneratePkgDef.targets b/eng/targets/GeneratePkgDef.targets index 1fb9f2075f218..dff341435fa2f 100644 --- a/eng/targets/GeneratePkgDef.targets +++ b/eng/targets/GeneratePkgDef.targets @@ -96,6 +96,7 @@ BeforeTargets="GeneratePkgDef"> + <_Version Condition="'%(PkgDefBrokeredService.Version)' != ''">\%(PkgDefBrokeredService.Version) <_Audience>dword:00000003 <_Audience Condition="'%(PkgDefBrokeredService.Audience)' == 'Process'">dword:00000001 <_Audience Condition="'%(PkgDefBrokeredService.Audience)' == 'RemoteExclusiveClient'">dword:00000100 @@ -231,7 +232,7 @@ <_PkgDefEntry Include="@(PkgDefBrokeredService)" Condition="'%(PkgDefBrokeredService.ProfferingPackageId)' == ''"> - - diff --git a/eng/targets/GenerateServiceHubConfigurationFiles.targets b/eng/targets/GenerateServiceHubConfigurationFiles.targets index a19cb268d1d0d..977eb704777ce 100644 --- a/eng/targets/GenerateServiceHubConfigurationFiles.targets +++ b/eng/targets/GenerateServiceHubConfigurationFiles.targets @@ -10,32 +10,9 @@ - - <_ServicesWithSuffix Include="@(ServiceHubService)" FileSuffix="64" HostIdSuffix="" /> - <_ServicesWithSuffix Include="@(ServiceHubService)" FileSuffix="64S" HostIdSuffix="S" /> - - - - - - - - - + <_ServicesWithSuffix Include="@(ServiceHubService)" FileSuffix="Core64" HostIdSuffix="" /> - <_ServicesWithSuffix Include="@(ServiceHubService)" FileSuffix="Core64S" HostIdSuffix="S" /> + <_ServicesWithSuffix Include="@(ServiceHubService)" FileSuffix="Core64S" HostIdSuffix="S" /> diff --git a/eng/targets/Imports.targets b/eng/targets/Imports.targets index 0fddfdc7a1a36..46eb0380702c3 100644 --- a/eng/targets/Imports.targets +++ b/eng/targets/Imports.targets @@ -208,6 +208,14 @@ + + + + + diff --git a/eng/targets/Services.props b/eng/targets/Services.props index 3712bf923d769..7a1dd4ad79358 100644 --- a/eng/targets/Services.props +++ b/eng/targets/Services.props @@ -44,6 +44,7 @@ + - - - diff --git a/eng/test-rebuild.ps1 b/eng/test-rebuild.ps1 index b6c52e0c349ec..05ebf1c658262 100644 --- a/eng/test-rebuild.ps1 +++ b/eng/test-rebuild.ps1 @@ -65,6 +65,10 @@ try { " --exclude net472\Zip\tools\vsixexpinstaller\System.ValueTuple.dll" + " --exclude net472\Zip\tools\vsixexpinstaller\VSIXExpInstaller.exe" + + # Semantic Search reference assemblies can't be reconstructed from source. + # The assemblies are not marked with ReferenceAssemblyAttribute attribute. + " --exclude net8.0\GeneratedRefAssemblies\Microsoft.CodeAnalysis.dll" + + " --debugPath `"$ArtifactsDir/BuildValidator`"" + " --sourcePath `"$RepoRoot/`"" + " --referencesPath `"$ArtifactsDir/bin`"" + diff --git a/global.json b/global.json index f323d2b0bb65e..3371f1e96e480 100644 --- a/global.json +++ b/global.json @@ -12,8 +12,8 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24161.1", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24161.1", + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24204.3", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24204.3", "Microsoft.Build.Traversal": "3.4.0" } } diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems index c17c11daeeec8..985079e04d290 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems @@ -29,6 +29,7 @@ + diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx index a0493c14e1860..fe85b481cfb79 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx @@ -412,4 +412,10 @@ Use primary constructor + + Anonymous function can be made static + + + Make anonymous function static + \ No newline at end of file diff --git a/src/Analyzers/CSharp/Analyzers/CodeStyle/CSharpAnalyzerOptionsProvider.cs b/src/Analyzers/CSharp/Analyzers/CodeStyle/CSharpAnalyzerOptionsProvider.cs index a5d8035dd759d..f10a26fc8ad69 100644 --- a/src/Analyzers/CSharp/Analyzers/CodeStyle/CSharpAnalyzerOptionsProvider.cs +++ b/src/Analyzers/CSharp/Analyzers/CodeStyle/CSharpAnalyzerOptionsProvider.cs @@ -92,6 +92,7 @@ internal CSharpCodeGenerationOptions GetCodeGenerationOptions() public CodeStyleOption2 PreferReadOnlyStruct => GetOption(CSharpCodeStyleOptions.PreferReadOnlyStruct, FallbackCodeStyleOptions.PreferReadOnlyStruct); public CodeStyleOption2 PreferReadOnlyStructMember => GetOption(CSharpCodeStyleOptions.PreferReadOnlyStructMember, FallbackCodeStyleOptions.PreferReadOnlyStructMember); public CodeStyleOption2 PreferStaticLocalFunction => GetOption(CSharpCodeStyleOptions.PreferStaticLocalFunction, FallbackCodeStyleOptions.PreferStaticLocalFunction); + public CodeStyleOption2 PreferStaticAnonymousFunction => GetOption(CSharpCodeStyleOptions.PreferStaticAnonymousFunction, FallbackCodeStyleOptions.PreferStaticAnonymousFunction); private TValue GetOption(Option2 option, TValue defaultValue) => _options.GetOption(option, defaultValue); diff --git a/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000..d1edd47f80269 --- /dev/null +++ b/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.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 Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.MakeAnonymousFunctionStatic; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +internal sealed class MakeAnonymousFunctionStaticDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer +{ + public MakeAnonymousFunctionStaticDiagnosticAnalyzer() + : base(IDEDiagnosticIds.MakeAnonymousFunctionStaticDiagnosticId, + EnforceOnBuildValues.MakeAnonymousFunctionStatic, + CSharpCodeStyleOptions.PreferStaticAnonymousFunction, + new LocalizableResourceString(nameof(CSharpAnalyzersResources.Make_anonymous_function_static), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)), + new LocalizableResourceString(nameof(CSharpAnalyzersResources.Anonymous_function_can_be_made_static), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources))) + { + } + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() + => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; + + protected override void InitializeWorker(AnalysisContext context) + { + context.RegisterCompilationStartAction(context => + { + if (context.Compilation.LanguageVersion() < LanguageVersion.CSharp9) + return; + + context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.AnonymousMethodExpression); + }); + } + + private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) + { + var option = context.GetCSharpAnalyzerOptions().PreferStaticAnonymousFunction; + if (!option.Value || ShouldSkipAnalysis(context, option.Notification)) + return; + + var anonymousFunction = (AnonymousFunctionExpressionSyntax)context.Node; + if (anonymousFunction.Modifiers.Any(SyntaxKind.StaticKeyword)) + return; + + if (context.SemanticModel.AnalyzeDataFlow(anonymousFunction) is { Succeeded: true, Captured.IsEmpty: true }) + { + context.ReportDiagnostic( + Diagnostic.Create( + Descriptor, + anonymousFunction.GetLocation())); + } + } +} diff --git a/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs index 0295d3a9908cc..7072dff8597cf 100644 --- a/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs @@ -3,15 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.CSharp.MisplacedUsingDirectives; @@ -40,9 +37,11 @@ internal sealed class MisplacedUsingDirectivesDiagnosticAnalyzer : AbstractBuilt s_localizableTitle, s_localizableInsideMessage); public MisplacedUsingDirectivesDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_outsideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement) - .Add(s_insideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement)) + : base( + [ + (s_outsideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement), + (s_insideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement) + ]) { } diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryNullableDirective/CSharpRemoveRedundantNullableDirectiveDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryNullableDirective/CSharpRemoveRedundantNullableDirectiveDiagnosticAnalyzer.cs index cb8f66ba88c65..e7f238402a12d 100644 --- a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryNullableDirective/CSharpRemoveRedundantNullableDirectiveDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryNullableDirective/CSharpRemoveRedundantNullableDirectiveDiagnosticAnalyzer.cs @@ -67,9 +67,8 @@ private void ProcessSyntaxTree( stack.Push(root); - while (stack.Count > 0) + while (stack.TryPop(out var current)) { - var current = stack.Pop(); if (!current.ContainsDirectives) continue; diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer.cs index 7e75cfc1109e9..8b183abf718a8 100644 --- a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryParentheses/CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer.cs @@ -54,7 +54,7 @@ public static bool CanRemoveParenthesesHelper( ExpressionSyntax parentExpression; switch (parenthesizedExpression.Parent) { - case ConditionalExpressionSyntax _: + case ConditionalExpressionSyntax: // If our parent is a conditional, then only remove parens if the inner // expression is a primary. i.e. it's ok to remove any of the following: // diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs index 7fe59d662f13d..b1daeecb8190a 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.UseCollectionInitializer; @@ -22,18 +21,15 @@ internal abstract class AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer public static readonly ImmutableDictionary ChangesSemantics = ImmutableDictionary.Empty.Add(UseCollectionInitializerHelpers.ChangesSemanticsName, ""); - protected new readonly DiagnosticDescriptor Descriptor; protected readonly DiagnosticDescriptor UnnecessaryCodeDescriptor; protected AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer(string diagnosticId, EnforceOnBuild enforceOnBuild) - : base(ImmutableDictionary.Empty - // Ugly hack. We need to create a descriptor to pass to our base *and* assign to one of our fields. - // The conditional pattern form lets us do that. - .Add(CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: false) is var descriptor ? descriptor : null, CodeStyleOptions2.PreferCollectionExpression) - .Add(CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: true) is var unnecessaryCodeDescriptor ? unnecessaryCodeDescriptor : null, CodeStyleOptions2.PreferCollectionExpression)) + : base( + [ + (CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: false), CodeStyleOptions2.PreferCollectionExpression) + ]) { - Descriptor = descriptor; - UnnecessaryCodeDescriptor = unnecessaryCodeDescriptor; + UnnecessaryCodeDescriptor = CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: true); } private static DiagnosticDescriptor CreateDescriptor(string diagnosticId, EnforceOnBuild enforceOnBuild, bool isUnnecessary) diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer.cs index a91372467c795..52084b99e4540 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer.cs @@ -21,6 +21,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseCollectionExpression; using static UseCollectionExpressionHelpers; +using static SyntaxFactory; using FluentState = UpdateExpressionState; [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -42,7 +43,7 @@ internal sealed partial class CSharpUseCollectionExpressionForFluentDiagnosticAn nameof(Array), nameof(Span), nameof(ReadOnlySpan), - nameof(List), + nameof(System.Collections.Generic.List), nameof(HashSet), nameof(LinkedList), nameof(Queue), @@ -178,10 +179,9 @@ private static bool AnalyzeInvocation( var copiedData = false; - while (stack.Count > 0) + while (stack.TryPop(out var current)) { cancellationToken.ThrowIfCancellationRequested(); - var current = stack.Pop(); // Methods of the form Add(...)/AddRange(...) or `ToXXX()` count as something to continue recursing down the // left hand side of the expression. In the inner expressions we can have things like `.Concat/.Append` @@ -293,7 +293,7 @@ void AddFinalMatch(ExpressionSyntax expression) { // Remove any whitespace around the `.`, making the singly-wrapped fluent expression into a single line. matchesInReverse.Add(new CollectionExpressionMatch( - SyntaxFactory.Argument(innerInvocation.WithExpression( + Argument(innerInvocation.WithExpression( memberAccess.Update( memberAccess.Expression.WithoutTrailingTrivia(), memberAccess.OperatorToken.WithoutTrivia(), @@ -302,7 +302,7 @@ void AddFinalMatch(ExpressionSyntax expression) return; } - matchesInReverse.Add(new CollectionExpressionMatch(SyntaxFactory.Argument(expression), UseSpread: true)); + matchesInReverse.Add(new CollectionExpressionMatch(Argument(expression), UseSpread: true)); } // We only want to offer this feature when the original collection was list-like (as opposed to being something diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs index 9a08888a4ef41..91530f6c07c7c 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs @@ -5,12 +5,14 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -19,6 +21,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseCollectionExpression; +using static CSharpSyntaxTokens; using static SyntaxFactory; internal static class UseCollectionExpressionHelpers @@ -106,6 +109,10 @@ public static bool CanReplaceWithCollectionExpression( return false; } + var operation = semanticModel.GetOperation(topMostExpression, cancellationToken); + if (operation?.Parent is IAssignmentOperation { Type.TypeKind: TypeKind.Dynamic }) + return false; + // HACK: Workaround lack of compiler information for collection expression conversions with casts. // Specifically, hardcode in knowledge that a cast to a constructible collection type of the empty collection // expression will always succeed, and there's no need to actually validate semantics there. @@ -209,7 +216,16 @@ bool IsSafeConversionWhenTypesDoNotMatch(out bool changesSemantics) // `IEnumerable obj = Array.Empty();` or // `IEnumerable obj = new[] { "" };` if (IsWellKnownCollectionInterface(convertedType) && type.AllInterfaces.Contains(convertedType)) + { + // The observable collections are known to have significantly different behavior than List. So + // disallow converting those types to ensure semantics are preserved. We do this even though + // allowSemanticsChange is true because this will basically be certain to break semantics, as opposed to + // the more common case where semantics may change slightly, but likely not in a way that breaks code. + if (type.Name is nameof(ObservableCollection) or nameof(ReadOnlyObservableCollection)) + return false; + return true; + } // Implicit reference array conversion is acceptable if the user is ok with semantics changing. For example: // @@ -675,10 +691,10 @@ public static CollectionExpressionSyntax ConvertInitializerToCollectionExpressio InitializerExpressionSyntax initializer, bool wasOnSingleLine) { // if the initializer is already on multiple lines, keep it that way. otherwise, squash from `{ 1, 2, 3 }` to `[1, 2, 3]` - var openBracket = Token(SyntaxKind.OpenBracketToken).WithTriviaFrom(initializer.OpenBraceToken); + var openBracket = OpenBracketToken.WithTriviaFrom(initializer.OpenBraceToken); var elements = initializer.Expressions.GetWithSeparators().SelectAsArray( i => i.IsToken ? i : ExpressionElement((ExpressionSyntax)i.AsNode()!)); - var closeBracket = Token(SyntaxKind.CloseBracketToken).WithTriviaFrom(initializer.CloseBraceToken); + var closeBracket = CloseBracketToken.WithTriviaFrom(initializer.CloseBraceToken); // If it was on a single line to begin with, then remove the inner spaces on the `{ ... }` to create `[...]`. If // it was multiline, leave alone as we want the brackets to just replace the existing braces exactly as they are. @@ -890,7 +906,7 @@ public static ImmutableArray> TryGetM return default; } - return matches.ToImmutable(); + return matches.ToImmutableAndClear(); } public static bool IsCollectionFactoryCreate( diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUpdateExpressionSyntaxHelper.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUpdateExpressionSyntaxHelper.cs index 79de454fef8ff..cea36e32a7aa2 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUpdateExpressionSyntaxHelper.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUpdateExpressionSyntaxHelper.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.UseCollectionInitializer; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.UseCollectionInitializer; @@ -40,5 +39,5 @@ public void GetPartsOfIfStatement( } private static IEnumerable ExtractEmbeddedStatements(StatementSyntax embeddedStatement) - => embeddedStatement is BlockSyntax block ? block.Statements : SpecializedCollections.SingletonEnumerable(embeddedStatement); + => embeddedStatement is BlockSyntax block ? [.. block.Statements] : [embeddedStatement]; } diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs index 6873b47873a82..53127b96f51f7 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -17,6 +16,9 @@ namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + /// /// Helper class that allows us to share lots of logic between the diagnostic analyzer and the /// code refactoring provider. Those can't share a common base class due to their own inheritance @@ -160,7 +162,7 @@ protected bool TryConvertToExpressionBodyForBaseProperty( if (getAccessor?.ExpressionBody != null && BlockSyntaxExtensions.MatchesPreference(getAccessor.ExpressionBody.Expression, conversionPreference)) { - arrowExpression = SyntaxFactory.ArrowExpressionClause(getAccessor.ExpressionBody.Expression); + arrowExpression = ArrowExpressionClause(getAccessor.ExpressionBody.Expression); semicolonToken = getAccessor.SemicolonToken; return true; } @@ -180,7 +182,7 @@ public bool CanOfferUseBlockBody( expressionBody = GetExpressionBody(declaration); if (expressionBody?.TryConvertToBlock( - SyntaxFactory.Token(SyntaxKind.SemicolonToken), false, block: out _) != true) + SemicolonToken, false, block: out _) != true) { fixesError = false; return false; @@ -296,13 +298,13 @@ protected TDeclaration WithAccessorList(SemanticModel semanticModel, TDeclaratio CreateReturnStatementForExpression(semanticModel, declaration), out var block); - var accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration); + var accessor = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration); accessor = block != null ? accessor.WithBody(block) : accessor.WithExpressionBody(expressionBody) .WithSemicolonToken(semicolonToken); - return WithAccessorList(declaration, SyntaxFactory.AccessorList([accessor])); + return WithAccessorList(declaration, AccessorList([accessor])); } protected virtual TDeclaration WithAccessorList(TDeclaration declaration, AccessorListSyntax accessorListSyntax) diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs index bed23c5b6d1a6..fa00a7e87a56b 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeGeneration; @@ -14,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody; [DiagnosticAnalyzer(LanguageNames.CSharp)] -internal class UseExpressionBodyDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer +internal sealed class UseExpressionBodyDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer { public const string FixesError = nameof(FixesError); @@ -28,16 +27,16 @@ public UseExpressionBodyDiagnosticAnalyzer() _syntaxKinds = _helpers.SelectManyAsArray(h => h.SyntaxKinds); } - private static ImmutableDictionary GetSupportedDescriptorsWithOptions() + private static ImmutableArray<(DiagnosticDescriptor, IOption2)> GetSupportedDescriptorsWithOptions() { - var builder = ImmutableDictionary.CreateBuilder(); + var builder = new FixedSizeArrayBuilder<(DiagnosticDescriptor, IOption2)>(_helpers.Length); foreach (var helper in _helpers) { var descriptor = CreateDescriptorWithId(helper.DiagnosticId, helper.EnforceOnBuild, hasAnyCodeStyleOption: true, helper.UseExpressionBodyTitle, helper.UseExpressionBodyTitle); - builder.Add(descriptor, helper.Option); + builder.Add((descriptor, helper.Option)); } - return builder.ToImmutable(); + return builder.MoveToImmutable(); } public override DiagnosticAnalyzerCategory GetAnalyzerCategory() diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs index d4248991e4b42..21af7ba3f40bd 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; @@ -21,9 +20,10 @@ internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : AbstractBui private static readonly DiagnosticDescriptor s_useBlockBodyForLambda = CreateDescriptorWithId(UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle, UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle); public UseExpressionBodyForLambdaDiagnosticAnalyzer() : base( - ImmutableDictionary.Empty - .Add(s_useExpressionBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas) - .Add(s_useBlockBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas)) + [ + (s_useExpressionBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas), + (s_useBlockBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas) + ]) { } diff --git a/src/Analyzers/CSharp/Analyzers/UsePatternCombinators/AnalyzedPattern.cs b/src/Analyzers/CSharp/Analyzers/UsePatternCombinators/AnalyzedPattern.cs index 63ad0ed28673d..3fec548f72e15 100644 --- a/src/Analyzers/CSharp/Analyzers/UsePatternCombinators/AnalyzedPattern.cs +++ b/src/Analyzers/CSharp/Analyzers/UsePatternCombinators/AnalyzedPattern.cs @@ -9,6 +9,8 @@ namespace Microsoft.CodeAnalysis.CSharp.UsePatternCombinators; +using static SyntaxFactory; + /// /// Base class to represent a pattern constructed from various checks /// @@ -48,12 +50,12 @@ internal sealed class Type : AnalyzedPattern // semantics, and for 'C.X' could be a compile error. // // So lets create a pattern syntax and make sure the result is the same - var dummyStatement = SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( + var dummyStatement = ExpressionStatement(AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, - SyntaxFactory.IdentifierName("_"), - SyntaxFactory.IsPatternExpression( + IdentifierName("_"), + IsPatternExpression( binaryExpression.Left, - SyntaxFactory.ConstantPattern(SyntaxFactory.ParenthesizedExpression(binaryExpression.Right.WithAdditionalAnnotations(s_annotation))) + ConstantPattern(ParenthesizedExpression(binaryExpression.Right.WithAdditionalAnnotations(s_annotation))) ) )); @@ -149,7 +151,7 @@ private Binary(AnalyzedPattern leftPattern, AnalyzedPattern rightPattern, bool i return null; var compareTarget = target == leftTarget ? rightTarget : leftTarget; - if (!SyntaxFactory.AreEquivalent(target.Syntax, compareTarget.Syntax)) + if (!AreEquivalent(target.Syntax, compareTarget.Syntax)) return null; return new Binary(leftPattern, rightPattern, isDisjunctive, token, target); diff --git a/src/Analyzers/CSharp/Analyzers/UsePatternMatching/CSharpIsAndCastCheckDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UsePatternMatching/CSharpIsAndCastCheckDiagnosticAnalyzer.cs index f14bdc9df2d5f..6c1872cccbd0e 100644 --- a/src/Analyzers/CSharp/Analyzers/UsePatternMatching/CSharpIsAndCastCheckDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UsePatternMatching/CSharpIsAndCastCheckDiagnosticAnalyzer.cs @@ -215,10 +215,8 @@ private static bool ContainsVariableDeclaration( using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(scope); - while (stack.Count > 0) + while (stack.TryPop(out var current)) { - var current = stack.Pop(); - if (current == variable) continue; diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf index a7a15178f83ab..48421f1d1df92 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf @@ -12,6 +12,11 @@ Přidat složené závorky do příkazu {0}. + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token Za tokenem klauzule výrazu šipky není povolen prázdný řádek. @@ -92,6 +97,11 @@ Výraz lambda je možné odebrat + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' Nastavit člena jako readonly @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + typeof se dá převést na nameof. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf index b5ec0a85191d3..2172b9847a4a9 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf @@ -12,6 +12,11 @@ Der Anweisung "{0}" geschweifte Klammern hinzufügen + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token Nach dem Token der Pfeilausdrucksklausel ist keine leere Zeile zulässig @@ -92,6 +97,11 @@ Lambdaausdruck kann entfernt werden + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' Member als "readonly" festlegen @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + "typeof" kann in "nameof" konvertiert werden diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf index 7a51869b61fbc..cd2bfeec89e08 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf @@ -12,6 +12,11 @@ Agregar llaves a la instrucción '{0}'. + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token No se permite una línea en blanco después del token de la cláusula de expresión de flecha @@ -92,6 +97,11 @@ Se puede quitar la expresión lambda + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' Convertir el miembro en 'readonly' @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + "typeof" puede convertirse en "nameof" diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf index 2af2443407a99..e979f3c4e5b5a 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf @@ -12,6 +12,11 @@ Ajouter des accolades à l'instruction '{0}'. + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token Ligne vide non autorisée après le jeton de clause d’expression fléchée @@ -92,6 +97,11 @@ L’expression lambda peut être supprimée. + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' Définir comme membre 'readonly' @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + « typeof » peut être converti en « nameof » diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf index f3bb72c10ca99..5e635c12f814a 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf @@ -12,6 +12,11 @@ Aggiunge le parentesi graffe all'istruzione '{0}'. + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token Riga vuota non consentita dopo il token della clausola dell'espressione arrow @@ -92,6 +97,11 @@ L'espressione lambda può essere rimossa + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' Rendi il membro 'readonly' @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + 'typeof' può essere convertito in 'nameof' diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf index e775f0b6a2511..9993870f89bfc 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf @@ -12,6 +12,11 @@ '{0}' ステートメントに波かっこを追加します。 + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token 矢印式句トークンの後に空白行は許可されていません @@ -92,6 +97,11 @@ ラムダ式を削除できます + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' メンバーを 'readonly' にする @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + 'typeof' を 'nameof' に変換できます diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf index 9bb8a212c10b9..c484dbf881fe0 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf @@ -12,6 +12,11 @@ '{0}' 문에 중괄호를 추가합니다. + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token 화살표 식 절 토큰 뒤에는 빈 줄을 사용할 수 없습니다. @@ -92,6 +97,11 @@ 람다 식을 제거할 수 있습니다. + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' 멤버를 'readonly'로 만들기 @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + 'typeof'를 'nameof'로 변환할 수 있습니다. diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf index 24ff89ea899fd..08f251bf46a7e 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf @@ -12,6 +12,11 @@ Dodaj nawiasy klamrowe do instrukcji „{0}”. + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token Pusty wiersz jest niedozwolony po tokenie klauzuli wyrażenia strzałki @@ -92,6 +97,11 @@ Wyrażenie lambda można usunąć + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' Ustaw element członkowski jako „readonly” @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + Element „typeof” można przekonwertować na element „nameof” diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf index 11ae62d4bbae9..ab1e47b022881 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf @@ -12,6 +12,11 @@ Adicionar chaves à instrução '{0}'. + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token Linha em branco não permitida após token de cláusula de expressão de seta @@ -92,6 +97,11 @@ A expressão lambda pode ser removida + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' Tornar membro 'readonly' @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + 'typeof' pode ser convertido em 'nameof' diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf index aac6d9dc7df5d..b6cf2a799b38c 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf @@ -12,6 +12,11 @@ Добавить фигурные скобки в оператор "{0}". + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token Пустая строка недопустима после маркера предложения выражения со стрелкой @@ -92,6 +97,11 @@ Лямбда-выражение можно удалить + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' Сделать элемент "readonly" @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + "typeof" можно преобразовать в "nameof". diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf index 0d353386d64aa..2d59415e93203 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf @@ -12,6 +12,11 @@ '{0}' deyimine küme ayracı ekleyin. + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token Ok ifadesi yan tümce belirtecinin ardından boş satıra izin verilmez @@ -92,6 +97,11 @@ Lambda ifadesi kaldırılabilir + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' Üyeyi 'readonly' yap @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + 'typeof' metodu 'nameof' metoduna dönüştürülebilir diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf index 6affe14302f04..62c96af13302f 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf @@ -12,6 +12,11 @@ 向 “{0}” 语句添加大括号。 + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token 箭头表达式子句标记后不允许空行 @@ -92,6 +97,11 @@ 可以删除 Lambda 表达式 + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' 将成员设为“readonly” @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + “Typeof”可以转换为“nameof” diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf index 8f83bfb1286d1..c4f8b46f0e909 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf @@ -12,6 +12,11 @@ 為 '{0}' 陳述式加入大括號。 + + Anonymous function can be made static + Anonymous function can be made static + + Blank line not allowed after arrow expression clause token 在箭頭運算式子句權杖後不允許空白行 @@ -92,6 +97,11 @@ 可移除 Lambda 運算式 + + Make anonymous function static + Make anonymous function static + + Make member 'readonly' 讓成員 'readonly' @@ -454,7 +464,7 @@ 'typeof' can be converted to 'nameof' - 'typeof' can be converted to 'nameof' + 'typeof' 可轉換為 'nameof' diff --git a/src/Analyzers/CSharp/CodeFixes/AddInheritdoc/AddInheritdocCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/AddInheritdoc/AddInheritdocCodeFixProvider.cs index 8ecb0f5a16653..97a19eb02677d 100644 --- a/src/Analyzers/CSharp/CodeFixes/AddInheritdoc/AddInheritdocCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/AddInheritdoc/AddInheritdocCodeFixProvider.cs @@ -3,30 +3,23 @@ // 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.CodeAnalysis; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp.Formatting; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.AddInheritdoc; +using static CSharpSyntaxTokens; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.AddInheritdoc), Shared] internal sealed class AddInheritdocCodeFixProvider : SyntaxEditorBasedCodeFixProvider { @@ -107,9 +100,9 @@ protected override async Task FixAllAsync(Document document, ImmutableArray comment. var xmlSpaceAfterTripleSlash = Token(leading: [DocumentationCommentExterior("///")], SyntaxKind.XmlTextLiteralToken, text: " ", valueText: " ", trailing: default); - var lessThanToken = Token(SyntaxKind.LessThanToken).WithoutTrivia(); + var lessThanToken = LessThanToken.WithoutTrivia(); var inheritdocTagName = XmlName("inheritdoc").WithoutTrivia(); - var slashGreaterThanToken = Token(SyntaxKind.SlashGreaterThanToken).WithoutTrivia(); + var slashGreaterThanToken = SlashGreaterThanToken.WithoutTrivia(); var xmlNewLineToken = Token(leading: default, SyntaxKind.XmlTextLiteralNewLineToken, text: newLine, valueText: newLine, trailing: default); var singleLineInheritdocComment = DocumentationCommentTrivia( @@ -120,7 +113,7 @@ protected override async Task FixAllAsync(Document document, ImmutableArray + diff --git a/src/Analyzers/CSharp/CodeFixes/ConditionalExpressionInStringInterpolation/CSharpAddParenthesesAroundConditionalExpressionInInterpolatedStringCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/ConditionalExpressionInStringInterpolation/CSharpAddParenthesesAroundConditionalExpressionInInterpolatedStringCodeFixProvider.cs index 33d5c2a114886..dae9100c4191b 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConditionalExpressionInStringInterpolation/CSharpAddParenthesesAroundConditionalExpressionInInterpolatedStringCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConditionalExpressionInStringInterpolation/CSharpAddParenthesesAroundConditionalExpressionInInterpolatedStringCodeFixProvider.cs @@ -18,6 +18,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ConditionalExpressionInStringInterpolation; +using static CSharpSyntaxTokens; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.AddParenthesesAroundConditionalExpressionInInterpolatedString), Shared] internal class CSharpAddParenthesesAroundConditionalExpressionInInterpolatedStringCodeFixProvider : CodeFixProvider { @@ -119,7 +121,7 @@ private static async Task InsertCloseParenthesisAsync( } var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var newCloseParen = SyntaxFactory.Token(SyntaxKind.CloseParenToken).WithTriviaFrom(parenthesizedExpression.CloseParenToken); + var newCloseParen = CloseParenToken.WithTriviaFrom(parenthesizedExpression.CloseParenToken); var parenthesizedExpressionWithClosingParen = parenthesizedExpression.WithCloseParenToken(newCloseParen); var newRoot = root.ReplaceNode(parenthesizedExpression, parenthesizedExpressionWithClosingParen); return document.WithSyntaxRoot(newRoot); diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceTransform.cs b/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceTransform.cs index 40175de4cf443..e4476ae00ee0a 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceTransform.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceTransform.cs @@ -18,6 +18,9 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertNamespace; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class ConvertNamespaceTransform { public static Task ConvertAsync(Document document, BaseNamespaceDeclarationSyntax baseNamespace, CSharpSyntaxFormattingOptions options, CancellationToken cancellationToken) @@ -271,7 +274,7 @@ private static SyntaxNode ReplaceWithBlockScopedNamespace( if (triviaBeforeSplit.Length > 0) { if (needsAdditionalLineEnding) - triviaBeforeSplit = triviaBeforeSplit.Append(SyntaxFactory.EndOfLine(lineEnding)); + triviaBeforeSplit = triviaBeforeSplit.Append(EndOfLine(lineEnding)); converted = converted.WithCloseBraceToken(converted.CloseBraceToken.WithPrependedLeadingTrivia(triviaBeforeSplit)); } @@ -320,7 +323,7 @@ private static bool HasLeadingBlankLine( private static FileScopedNamespaceDeclarationSyntax ConvertNamespaceDeclaration(NamespaceDeclarationSyntax namespaceDeclaration) { // If the open-brace token has any special trivia, then move them to after the semicolon. - var semiColon = SyntaxFactory.Token(SyntaxKind.SemicolonToken) + var semiColon = SemicolonToken .WithoutTrivia() .WithTrailingTrivia(namespaceDeclaration.Name.GetTrailingTrivia()) .WithAppendedTrailingTrivia(namespaceDeclaration.OpenBraceToken.LeadingTrivia); @@ -329,7 +332,7 @@ private static FileScopedNamespaceDeclarationSyntax ConvertNamespaceDeclaration( semiColon = semiColon.WithAppendedTrailingTrivia(namespaceDeclaration.OpenBraceToken.TrailingTrivia); // Move trivia after the original name token to now be after the new semicolon token. - var fileScopedNamespace = SyntaxFactory.FileScopedNamespaceDeclaration( + var fileScopedNamespace = FileScopedNamespaceDeclaration( namespaceDeclaration.AttributeLists, namespaceDeclaration.Modifiers, namespaceDeclaration.NamespaceKeyword, @@ -379,16 +382,16 @@ private static FileScopedNamespaceDeclarationSyntax ConvertNamespaceDeclaration( private static NamespaceDeclarationSyntax ConvertFileScopedNamespace(ParsedDocument document, FileScopedNamespaceDeclarationSyntax fileScopedNamespace, string lineEnding, NewLinePlacement newLinePlacement) { var nameSyntax = fileScopedNamespace.Name.WithAppendedTrailingTrivia(fileScopedNamespace.SemicolonToken.LeadingTrivia) - .WithAppendedTrailingTrivia(newLinePlacement.HasFlag(NewLinePlacement.BeforeOpenBraceInTypes) ? SyntaxFactory.EndOfLine(lineEnding) : SyntaxFactory.Space); - var openBraceToken = SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithoutLeadingTrivia().WithTrailingTrivia(fileScopedNamespace.SemicolonToken.TrailingTrivia); + .WithAppendedTrailingTrivia(newLinePlacement.HasFlag(NewLinePlacement.BeforeOpenBraceInTypes) ? EndOfLine(lineEnding) : Space); + var openBraceToken = OpenBraceToken.WithoutLeadingTrivia().WithTrailingTrivia(fileScopedNamespace.SemicolonToken.TrailingTrivia); if (openBraceToken.TrailingTrivia is not [.., SyntaxTrivia(SyntaxKind.EndOfLineTrivia)]) { - openBraceToken = openBraceToken.WithAppendedTrailingTrivia(SyntaxFactory.EndOfLine(lineEnding)); + openBraceToken = openBraceToken.WithAppendedTrailingTrivia(EndOfLine(lineEnding)); } FileScopedNamespaceDeclarationSyntax adjustedFileScopedNamespace; - var closeBraceToken = SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithoutLeadingTrivia().WithoutTrailingTrivia(); + var closeBraceToken = CloseBraceToken.WithoutLeadingTrivia().WithoutTrailingTrivia(); // Normally the block scoped namespace will have a newline after the closing brace. The only exception to // this occurs when there are no tokens after the closing brace and the document with a file scoped @@ -397,7 +400,7 @@ private static NamespaceDeclarationSyntax ConvertFileScopedNamespace(ParsedDocum if (!fileScopedNamespace.GetLastToken().GetNextTokenOrEndOfFile().IsKind(SyntaxKind.EndOfFileToken) || document.Text.Lines.GetLinePosition(document.Text.Length).Character == 0) { - closeBraceToken = closeBraceToken.WithAppendedTrailingTrivia(SyntaxFactory.EndOfLine(lineEnding)); + closeBraceToken = closeBraceToken.WithAppendedTrailingTrivia(EndOfLine(lineEnding)); adjustedFileScopedNamespace = fileScopedNamespace; } else @@ -405,7 +408,7 @@ private static NamespaceDeclarationSyntax ConvertFileScopedNamespace(ParsedDocum // Make sure the body of the file scoped namespace ends with a trailing new line (so the closing brace // of the converted block-body namespace appears on its own line), but don't add a new line after the // closing brace. - adjustedFileScopedNamespace = fileScopedNamespace.WithAppendedTrailingTrivia(SyntaxFactory.EndOfLine(lineEnding)); + adjustedFileScopedNamespace = fileScopedNamespace.WithAppendedTrailingTrivia(EndOfLine(lineEnding)); } // If the file scoped namespace is indented, also indent the newly added braces to match @@ -413,12 +416,12 @@ private static NamespaceDeclarationSyntax ConvertFileScopedNamespace(ParsedDocum if (outerIndentation.Length > 0) { if (newLinePlacement.HasFlag(NewLinePlacement.BeforeOpenBraceInTypes)) - openBraceToken = openBraceToken.WithLeadingTrivia(openBraceToken.LeadingTrivia.Add(SyntaxFactory.Whitespace(outerIndentation))); + openBraceToken = openBraceToken.WithLeadingTrivia(openBraceToken.LeadingTrivia.Add(Whitespace(outerIndentation))); - closeBraceToken = closeBraceToken.WithLeadingTrivia(closeBraceToken.LeadingTrivia.Add(SyntaxFactory.Whitespace(outerIndentation))); + closeBraceToken = closeBraceToken.WithLeadingTrivia(closeBraceToken.LeadingTrivia.Add(Whitespace(outerIndentation))); } - var namespaceDeclaration = SyntaxFactory.NamespaceDeclaration( + var namespaceDeclaration = NamespaceDeclaration( adjustedFileScopedNamespace.AttributeLists, adjustedFileScopedNamespace.Modifiers, adjustedFileScopedNamespace.NamespaceKeyword, diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs b/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs index 020adab72164d..032c0859edc56 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs @@ -14,6 +14,7 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression; using static ConvertSwitchStatementToExpressionHelpers; +using static CSharpSyntaxTokens; using static SyntaxFactory; internal sealed partial class ConvertSwitchStatementToExpressionCodeFixProvider @@ -73,12 +74,12 @@ private StatementSyntax GetFinalStatement( return ReturnStatement( Token(leadingTrivia, SyntaxKind.ReturnKeyword, trailing: default), switchExpression, - Token(SyntaxKind.SemicolonToken)); + SemicolonToken); case SyntaxKind.ThrowStatement: return ThrowStatement( Token(leadingTrivia, SyntaxKind.ThrowKeyword, trailing: default), switchExpression, - Token(SyntaxKind.SemicolonToken)); + SemicolonToken); } Debug.Assert(SyntaxFacts.IsAssignmentExpression(nodeToGenerate)); @@ -250,8 +251,8 @@ private ExpressionSyntax RewriteSwitchStatement( 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)))), - Token(SyntaxKind.CloseBraceToken)); + switchArms.Select(t => CommaToken.WithTrailingTrivia(t.tokensForTrailingTrivia.GetTrivia().FilterComments(addElasticMarker: true)))), + CloseBraceToken); } private SwitchStatementSyntax AddCastIfNecessary(SwitchStatementSyntax node) diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordEngine.cs b/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordEngine.cs index e3ed6efd59edb..b785edb46c8f1 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordEngine.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordEngine.cs @@ -20,6 +20,9 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertToRecord; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class ConvertToRecordEngine { private const SyntaxRemoveOptions RemovalOptions = @@ -124,8 +127,8 @@ await RefactorInitializersAsync(type, solutionEditor, propertiesToAssign, cancel // add an initializer that links the property to the primary constructor parameter documentEditor.ReplaceNode(property, property .WithInitializer( - SyntaxFactory.EqualsValueClause(SyntaxFactory.IdentifierName(property.Identifier.WithoutTrivia()))) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))); + EqualsValueClause(IdentifierName(property.Identifier.WithoutTrivia()))) + .WithSemicolonToken(SemicolonToken)); } else { @@ -186,9 +189,9 @@ await RefactorInitializersAsync(type, solutionEditor, propertiesToAssign, cancel var modifiedConstructor = constructor .RemoveNodes(expressionStatementsToRemove, RemovalOptions)! - .WithInitializer(SyntaxFactory.ConstructorInitializer( + .WithInitializer(ConstructorInitializer( SyntaxKind.ThisConstructorInitializer, - SyntaxFactory.ArgumentList([.. expressions.Select(SyntaxFactory.Argument)]))); + ArgumentList([.. expressions.Select(Argument)]))); documentEditor.ReplaceNode(constructor, modifiedConstructor); } @@ -251,10 +254,10 @@ await RefactorInitializersAsync(type, solutionEditor, propertiesToAssign, cancel ? result.Symbol.Type.GenerateTypeSyntax() : result.Declaration.Type; var identifier = result.IsInherited - ? SyntaxFactory.Identifier(result.Symbol.Name) + ? Identifier(result.Symbol.Name) : result.Declaration.Identifier; - return SyntaxFactory.Parameter( + return Parameter( GetModifiedAttributeListsForProperty(result), modifiers: default, type, @@ -264,7 +267,7 @@ await RefactorInitializersAsync(type, solutionEditor, propertiesToAssign, cancel // if we have a class, move trivia from class keyword to record keyword // if struct, split trivia and leading goes to record keyword, trailing goes to struct keyword - var recordKeyword = SyntaxFactory.Token(SyntaxKind.RecordKeyword); + var recordKeyword = RecordKeyword; recordKeyword = type.TypeKind == TypeKind.Class ? recordKeyword.WithTriviaFrom(typeDeclaration.Keyword) : recordKeyword.WithLeadingTrivia(typeDeclaration.Keyword.LeadingTrivia); @@ -312,11 +315,11 @@ await RefactorInitializersAsync(type, solutionEditor, propertiesToAssign, cancel var inheritedPositionalParams = PositionalParameterInfo .GetInheritedPositionalParams(type, cancellationToken) .SelectAsArray(prop => - SyntaxFactory.Argument(SyntaxFactory.IdentifierName(prop.Name))); + Argument(IdentifierName(prop.Name))); typeList = typeList.Replace(baseRecord, - SyntaxFactory.PrimaryConstructorBaseType(baseRecord.Type.WithoutTrailingTrivia(), - SyntaxFactory.ArgumentList([.. inheritedPositionalParams]) + PrimaryConstructorBaseType(baseRecord.Type.WithoutTrailingTrivia(), + ArgumentList([.. inheritedPositionalParams]) .WithTrailingTrivia(baseTrailingTrivia))); } @@ -389,16 +392,16 @@ private static RecordDeclarationSyntax CreateRecordDeclaration( openBrace = default; closeBrace = default; semicolon = typeDeclaration.SemicolonToken == default - ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) + ? SemicolonToken : typeDeclaration.SemicolonToken; } else { openBrace = typeDeclaration.OpenBraceToken == default - ? SyntaxFactory.Token(SyntaxKind.OpenBraceToken) + ? OpenBraceToken : typeDeclaration.OpenBraceToken; closeBrace = typeDeclaration.CloseBraceToken == default - ? SyntaxFactory.Token(SyntaxKind.CloseBraceToken) + ? CloseBraceToken : typeDeclaration.CloseBraceToken; semicolon = default; @@ -409,7 +412,7 @@ private static RecordDeclarationSyntax CreateRecordDeclaration( typeDeclaration.Members[0], typeDeclaration.Members[0].GetNodeWithoutLeadingBlankLines()); } - return SyntaxFactory.RecordDeclaration( + return RecordDeclaration( type.TypeKind == TypeKind.Class ? SyntaxKind.RecordDeclaration : SyntaxKind.RecordStructDeclaration, @@ -418,11 +421,11 @@ private static RecordDeclarationSyntax CreateRecordDeclaration( recordKeyword, type.TypeKind == TypeKind.Class ? default - : typeDeclaration.Keyword.WithTrailingTrivia(SyntaxFactory.ElasticMarker), + : typeDeclaration.Keyword.WithTrailingTrivia(ElasticMarker), // remove trailing trivia from places where we would want to insert the parameter list before a line break - typeDeclaration.Identifier.WithTrailingTrivia(SyntaxFactory.ElasticMarker), - typeDeclaration.TypeParameterList?.WithTrailingTrivia(SyntaxFactory.ElasticMarker), - SyntaxFactory.ParameterList([.. propertiesToAddAsParams]) + typeDeclaration.Identifier.WithTrailingTrivia(ElasticMarker), + typeDeclaration.TypeParameterList?.WithTrailingTrivia(ElasticMarker), + ParameterList([.. propertiesToAddAsParams]) .WithAppendedTrailingTrivia(constructorTrivia), baseList, typeDeclaration.ConstraintClauses, @@ -457,7 +460,7 @@ private static SyntaxList GetModifiedAttributeListsForPrope { // convert attributes attached to the property with no target into "property :" targeted attributes return attributeList - .WithTarget(SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.PropertyKeyword))) + .WithTarget(AttributeTargetSpecifier(PropertyKeyword)) .WithoutTrivia(); } else @@ -553,10 +556,10 @@ private static async Task RefactorInitializersAsync( // replace: new C { Foo = 0; Bar = false; }; // with: new C(0, false); - return SyntaxFactory.ObjectCreationExpression( + return ObjectCreationExpression( updatedObjectCreation.NewKeyword, updatedObjectCreation.Type.WithoutTrailingTrivia(), - SyntaxFactory.ArgumentList([.. updatedExpressions.Select(expression => SyntaxFactory.Argument(expression.WithoutTrivia()))]), + ArgumentList([.. updatedExpressions.Select(expression => Argument(expression.WithoutTrivia()))]), newInitializer); }); } @@ -645,28 +648,28 @@ node is XmlElementSyntax element && propDoc.IsMultilineDocComment()) { // add /** and */ - newClassDocComment = SyntaxFactory.DocumentationCommentTrivia( + newClassDocComment = DocumentationCommentTrivia( SyntaxKind.MultiLineDocumentationCommentTrivia, // Our parameter method gives a newline (without leading trivia) to start // because we assume we're following some other comment, we replace that newline to add // the start of comment leading trivia as well since we're not following another comment [.. propertyParamComments.Skip(1) - .Prepend(SyntaxFactory.XmlText(SyntaxFactory.XmlTextNewLine(lineFormattingOptions.NewLine, continueXmlDocumentationComment: false) - .WithLeadingTrivia(SyntaxFactory.DocumentationCommentExterior("/**")) + .Prepend(XmlText(XmlTextNewLine(lineFormattingOptions.NewLine, continueXmlDocumentationComment: false) + .WithLeadingTrivia(DocumentationCommentExterior("/**")) .WithTrailingTrivia(exteriorTrivia))) - .Append(SyntaxFactory.XmlText(SyntaxFactory.XmlTextNewLine(lineFormattingOptions.NewLine, continueXmlDocumentationComment: false)))], - SyntaxFactory.Token(SyntaxKind.EndOfDocumentationCommentToken) - .WithTrailingTrivia(SyntaxFactory.DocumentationCommentExterior("*/"), SyntaxFactory.ElasticCarriageReturnLineFeed)); + .Append(XmlText(XmlTextNewLine(lineFormattingOptions.NewLine, continueXmlDocumentationComment: false)))], + EndOfDocumentationCommentToken + .WithTrailingTrivia(DocumentationCommentExterior("*/"), ElasticCarriageReturnLineFeed)); } else { // add extra line at end to end doc comment // also skip first newline and replace with non-newline - newClassDocComment = SyntaxFactory.DocumentationCommentTrivia( + newClassDocComment = DocumentationCommentTrivia( SyntaxKind.MultiLineDocumentationCommentTrivia, [.. propertyParamComments.Skip(1) - .Prepend(SyntaxFactory.XmlText(SyntaxFactory.XmlTextLiteral(" ").WithLeadingTrivia(exteriorTrivia)))]) - .WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed); + .Prepend(XmlText(XmlTextLiteral(" ").WithLeadingTrivia(exteriorTrivia)))]) + .WithAppendedTrailingTrivia(ElasticCarriageReturnLineFeed); } } @@ -677,14 +680,14 @@ [.. propertyParamComments.Skip(1) return [.. classTrivia .Where(trivia => !trivia.IsDocComment()) .Concat(propertyNonDocComments) - .Append(SyntaxFactory.Trivia(newClassDocComment)) + .Append(Trivia(newClassDocComment)) .Select(trivia => trivia.AsElastic())]; } else { // there were comments after doc comment return [.. classTrivia - .Replace(classDocComment.Value, SyntaxFactory.Trivia(newClassDocComment)) + .Replace(classDocComment.Value, Trivia(newClassDocComment)) .Concat(propertyNonDocComments) .Select(trivia => trivia.AsElastic())]; } @@ -756,15 +759,15 @@ private static IEnumerable CreateParamComments( // add an extra line and space with the exterior trivia, so that our params start on the next line and each // param goes on a new line with the continuation trivia // when adding a new line, the continue flag adds a single line documentation trivia, but we don't necessarily want that - yield return SyntaxFactory.XmlText( - SyntaxFactory.XmlTextNewLine(lineFormattingOptions.NewLine, continueXmlDocumentationComment: false), - SyntaxFactory.XmlTextLiteral(" ").WithLeadingTrivia(exteriorTrivia)); + yield return XmlText( + XmlTextNewLine(lineFormattingOptions.NewLine, continueXmlDocumentationComment: false), + XmlTextLiteral(" ").WithLeadingTrivia(exteriorTrivia)); if (result.IsInherited) { // generate a param comment with an inherited doc - yield return SyntaxFactory.XmlParamElement(result.Symbol.Name, SyntaxFactory.XmlEmptyElement( - SyntaxFactory.XmlName(DocumentationCommentXmlNames.InheritdocElementName))); + yield return XmlParamElement(result.Symbol.Name, XmlEmptyElement( + XmlName(DocumentationCommentXmlNames.InheritdocElementName))); } else { @@ -820,7 +823,7 @@ tokens is [(kind: SyntaxKind.XmlTextLiteralNewLineToken), _, ..]) } } - yield return SyntaxFactory.XmlParamElement(result.Declaration.Identifier.ValueText, paramContent.AsArray()); + yield return XmlParamElement(result.Declaration.Identifier.ValueText, paramContent.AsArray()); } } } diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordHelpers.cs b/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordHelpers.cs index 6bbb4a68f93a3..7778a8ec53176 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordHelpers.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordHelpers.cs @@ -533,7 +533,7 @@ private static ImmutableArray GetEqualizedFields( value, successRequirement: true, type, fields, out var _2)) { // we're done, no more statements to check - return fields.ToImmutable(); + return fields.ToImmutableAndClear(); } // check for the first statement as an explicit cast to a variable declaration // like: var otherC = other as C; @@ -557,7 +557,7 @@ private static ImmutableArray GetEqualizedFields( return []; } - return fields.ToImmutable(); + return fields.ToImmutableAndClear(); } /// diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/PositionalParameterInfo.cs b/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/PositionalParameterInfo.cs index 67af4f91edf23..e474aaa85e96c 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/PositionalParameterInfo.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/PositionalParameterInfo.cs @@ -68,7 +68,7 @@ public static ImmutableArray GetPropertiesForPositional _ => throw ExceptionUtilities.Unreachable(), }).WhereNotNull()); - return resultBuilder.ToImmutable(); + return resultBuilder.ToImmutableAndClear(); } public static ImmutableArray GetInheritedPositionalParams( diff --git a/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.AddNewKeywordAction.cs b/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.AddNewKeywordAction.cs index 20561b64cab2a..0c332830bd858 100644 --- a/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.AddNewKeywordAction.cs +++ b/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.AddNewKeywordAction.cs @@ -3,19 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Linq; -using System.Threading.Tasks; using System.Threading; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp.LanguageService; using Microsoft.CodeAnalysis.CSharp.OrderModifiers; using Microsoft.CodeAnalysis.OrderModifiers; -using Roslyn.Utilities; -using Microsoft.CodeAnalysis.CSharp.LanguageService; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.CodeActions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.HideBase; +using static CSharpSyntaxTokens; + internal partial class HideBaseCodeFixProvider { private class AddNewKeywordAction(Document document, SyntaxNode node, CodeActionOptionsProvider fallbackOptions) : CodeAction @@ -41,7 +41,7 @@ private static SyntaxNode GetNewNode(SyntaxNode node, string preferredModifierOr { var syntaxFacts = CSharpSyntaxFacts.Instance; var modifiers = syntaxFacts.GetModifiers(node); - var newModifiers = modifiers.Add(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + var newModifiers = modifiers.Add(NewKeyword); if (!CSharpOrderModifiersHelper.Instance.TryGetOrComputePreferredOrder(preferredModifierOrder, out var preferredOrder) || !AbstractOrderModifiersHelpers.IsOrdered(preferredOrder, modifiers)) diff --git a/src/Analyzers/CSharp/CodeFixes/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs index baa760caa134a..34ac23b0be67a 100644 --- a/src/Analyzers/CSharp/CodeFixes/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs @@ -4,7 +4,6 @@ #nullable disable -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; @@ -14,21 +13,20 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.InlineDeclaration; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.InlineDeclaration), Shared] internal partial class CSharpInlineDeclarationCodeFixProvider : SyntaxEditorBasedCodeFixProvider { @@ -72,14 +70,7 @@ protected override async Task FixAllAsync( await editor.ApplyExpressionLevelSemanticEditsAsync( document, originalNodes, - t => - { - using var _ = ArrayBuilder.GetInstance(capacity: 2, out var additionalNodesToTrack); - additionalNodesToTrack.Add(t.identifier); - additionalNodesToTrack.Add(t.declarator); - - return (t.invocationOrCreation, additionalNodesToTrack.ToImmutable()); - }, + static t => (t.invocationOrCreation, ImmutableArray.Create(t.identifier, t.declarator)), (_, _, _) => true, (semanticModel, currentRoot, t, currentNode) => ReplaceIdentifierWithInlineDeclaration( @@ -215,7 +206,7 @@ private static SyntaxNode ReplaceIdentifierWithInlineDeclaration( { editor.ReplaceNode( declaration.Type, - (t, g) => t.WithTrailingTrivia(SyntaxFactory.ElasticSpace).WithoutAnnotations(Formatter.Annotation)); + (t, g) => t.WithTrailingTrivia(ElasticSpace).WithoutAnnotations(Formatter.Annotation)); } } } @@ -243,7 +234,7 @@ private static SyntaxNode ReplaceIdentifierWithInlineDeclaration( // If the user originally wrote it something other than 'var', then use what they // wrote. Otherwise, synthesize the actual type of the local. var explicitType = declaration.Type.IsVar ? local.Type?.GenerateTypeSyntax() : declaration.Type; - declarationExpression = SyntaxFactory.DeclarationExpression(explicitType, declarationExpression.Designation); + declarationExpression = DeclarationExpression(explicitType, declarationExpression.Designation); } editor.ReplaceNode(identifier, declarationExpression); @@ -261,7 +252,7 @@ public static TypeSyntax GenerateTypeSyntaxOrVar( // analyze those due to limitations between how it uses Speculative SemanticModels // and how those don't handle new declarations well. return useVar - ? SyntaxFactory.IdentifierName("var") + ? IdentifierName("var") : symbol.GenerateTypeSyntax(); } @@ -281,7 +272,7 @@ private static DeclarationExpressionSyntax GetDeclarationExpression( SourceText sourceText, IdentifierNameSyntax identifier, TypeSyntax newType, VariableDeclaratorSyntax declaratorOpt) { - var designation = SyntaxFactory.SingleVariableDesignation(identifier.Identifier); + var designation = SingleVariableDesignation(identifier.Identifier); if (declaratorOpt != null) { @@ -308,10 +299,10 @@ private static DeclarationExpressionSyntax GetDeclarationExpression( // designation and in those cases adding elastic trivia will break formatting. if (!designation.HasLeadingTrivia) { - newType = newType.WithAppendedTrailingTrivia(SyntaxFactory.ElasticSpace); + newType = newType.WithAppendedTrailingTrivia(ElasticSpace); } - return SyntaxFactory.DeclarationExpression(newType, designation); + return DeclarationExpression(newType, designation); } private static IEnumerable MassageTrivia(IEnumerable triviaList) @@ -328,7 +319,7 @@ private static IEnumerable MassageTrivia(IEnumerable // indentation spaces to be inserted in the out-var location. It is appropriate // though to have single spaces to help separate out things like comments and // tokens though. - yield return SyntaxFactory.Space; + yield return Space; } } } diff --git a/src/Analyzers/CSharp/CodeFixes/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessCodeFixProvider.cs index d7a0179652e19..137af59d50036 100644 --- a/src/Analyzers/CSharp/CodeFixes/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/InvokeDelegateWithConditionalAccess/InvokeDelegateWithConditionalAccessCodeFixProvider.cs @@ -22,6 +22,8 @@ namespace Microsoft.CodeAnalysis.CSharp.InvokeDelegateWithConditionalAccess; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.InvokeDelegateWithConditionalAccess), Shared] internal partial class InvokeDelegateWithConditionalAccessCodeFixProvider : SyntaxEditorBasedCodeFixProvider { @@ -93,13 +95,13 @@ private static void HandleSingleIfStatementForm( var (invokedExpression, invokeName) = invocationExpression.Expression is MemberAccessExpressionSyntax { Name: IdentifierNameSyntax { Identifier.ValueText: nameof(Action.Invoke) } } memberAccessExpression ? (memberAccessExpression.Expression, memberAccessExpression.Name) - : (invocationExpression.Expression, SyntaxFactory.IdentifierName(nameof(Action.Invoke))); + : (invocationExpression.Expression, IdentifierName(nameof(Action.Invoke))); StatementSyntax newStatement = expressionStatement.WithExpression( - SyntaxFactory.ConditionalAccessExpression( + ConditionalAccessExpression( invokedExpression, - SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberBindingExpression(invokeName), invocationExpression.ArgumentList))); + InvocationExpression( + MemberBindingExpression(invokeName), invocationExpression.ArgumentList))); newStatement = newStatement.WithPrependedLeadingTrivia(ifStatement.GetLeadingTrivia()); if (ifStatement.Parent.IsKind(SyntaxKind.ElseClause) && @@ -140,13 +142,13 @@ private static void HandleVariableAndIfStatementForm( var invokeName = invocationExpression.Expression is MemberAccessExpressionSyntax { Name: IdentifierNameSyntax { Identifier.ValueText: nameof(Action.Invoke) } } memberAccessExpression ? memberAccessExpression.Name - : SyntaxFactory.IdentifierName(nameof(Action.Invoke)); + : IdentifierName(nameof(Action.Invoke)); var newStatement = expressionStatement.WithExpression( - SyntaxFactory.ConditionalAccessExpression( + ConditionalAccessExpression( localDeclarationStatement.Declaration.Variables[0].Initializer!.Value.Parenthesize(), - SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberBindingExpression(invokeName), invocationExpression.ArgumentList))); + InvocationExpression( + MemberBindingExpression(invokeName), invocationExpression.ArgumentList))); newStatement = newStatement.WithAdditionalAnnotations(Formatter.Annotation); newStatement = AppendTriviaWithoutEndOfLines(newStatement, ifStatement); diff --git a/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs new file mode 100644 index 0000000000000..4691f825733e9 --- /dev/null +++ b/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.CSharp.MakeAnonymousFunctionStatic; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.MakeAnonymousFunctionStatic), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpMakeAnonymousFunctionStaticCodeFixProvider() : SyntaxEditorBasedCodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds { get; } = + [IDEDiagnosticIds.MakeAnonymousFunctionStaticDiagnosticId]; + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + RegisterCodeFix( + context, + CSharpAnalyzersResources.Make_anonymous_function_static, + nameof(CSharpAnalyzersResources.Make_anonymous_function_static), + context.Diagnostics[0].Severity > DiagnosticSeverity.Hidden ? CodeActionPriority.Default : CodeActionPriority.Low); + + return Task.CompletedTask; + } + + protected override Task FixAllAsync(Document document, ImmutableArray diagnostics, SyntaxEditor editor, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) + { + var generator = editor.Generator; + + foreach (var diagnostic in diagnostics) + { + var anonymousFunction = diagnostic.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); + editor.ReplaceNode(anonymousFunction, static (node, generator) => generator.WithModifiers(node, generator.GetModifiers(node).WithIsStatic(true))); + } + + return Task.CompletedTask; + } +} diff --git a/src/Analyzers/CSharp/CodeFixes/MakeMemberRequired/CSharpMakeMemberRequiredCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/MakeMemberRequired/CSharpMakeMemberRequiredCodeFixProvider.cs index 039b5cd452721..835ec2a1a3653 100644 --- a/src/Analyzers/CSharp/CodeFixes/MakeMemberRequired/CSharpMakeMemberRequiredCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/MakeMemberRequired/CSharpMakeMemberRequiredCodeFixProvider.cs @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.MakeMemberRequired; [ExtensionOrder(Before = PredefinedCodeFixProviderNames.DeclareAsNullable)] internal sealed class CSharpMakeMemberRequiredCodeFixProvider : SyntaxEditorBasedCodeFixProvider { - private const string CS8618 = nameof(CS8618); // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable. + private const string CS8618 = nameof(CS8618); // Non-nullable variable must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring it as nullable. [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] diff --git a/src/Analyzers/CSharp/CodeFixes/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs index 40fd90e48f730..b23010c17f6a6 100644 --- a/src/Analyzers/CSharp/CodeFixes/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs @@ -14,10 +14,12 @@ using Microsoft.CodeAnalysis.MakeMethodAsynchronous; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.MakeMethodAsynchronous; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.AddAsync), Shared] internal class CSharpMakeMethodAsynchronousCodeFixProvider : AbstractMakeMethodAsynchronousCodeFixProvider { @@ -26,8 +28,6 @@ internal class CSharpMakeMethodAsynchronousCodeFixProvider : AbstractMakeMethodA private const string CS4034 = nameof(CS4034); // The 'await' operator can only be used within an async lambda expression. Consider marking this method with the 'async' modifier. private const string CS0246 = nameof(CS0246); // The type or namespace name 'await' could not be found - private static readonly SyntaxToken s_asyncToken = SyntaxFactory.Token(SyntaxKind.AsyncKeyword); - [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public CSharpMakeMethodAsynchronousCodeFixProvider() @@ -155,9 +155,9 @@ private static TypeSyntax FixMethodReturnType( static TypeSyntax MakeGenericType(string type, ITypeSymbol typeArgumentFrom) { - var result = SyntaxFactory.GenericName( - SyntaxFactory.Identifier(type), - SyntaxFactory.TypeArgumentList([typeArgumentFrom.GetTypeArguments()[0].GenerateTypeSyntax()])); + var result = GenericName( + Identifier(type), + TypeArgumentList([typeArgumentFrom.GetTypeArguments()[0].GenerateTypeSyntax()])); return result.WithAdditionalAnnotations(Simplifier.Annotation); } @@ -179,10 +179,10 @@ private static bool IsIEnumerator(ITypeSymbol returnType, KnownTaskTypes knownTy private static SyntaxTokenList AddAsyncModifierWithCorrectedTrivia(SyntaxTokenList modifiers, ref TypeSyntax newReturnType) { if (modifiers.Any()) - return modifiers.Add(s_asyncToken); + return modifiers.Add(AsyncKeyword); // Move the leading trivia from the return type to the new modifiers list. - var result = SyntaxFactory.TokenList(s_asyncToken.WithLeadingTrivia(newReturnType.GetLeadingTrivia())); + var result = TokenList(AsyncKeyword.WithLeadingTrivia(newReturnType.GetLeadingTrivia())); newReturnType = newReturnType.WithoutLeadingTrivia(); return result; } @@ -190,5 +190,5 @@ private static SyntaxTokenList AddAsyncModifierWithCorrectedTrivia(SyntaxTokenLi private static AnonymousFunctionExpressionSyntax FixAnonymousFunction(AnonymousFunctionExpressionSyntax anonymous) => anonymous .WithoutLeadingTrivia() - .WithAsyncKeyword(s_asyncToken.WithPrependedLeadingTrivia(anonymous.GetLeadingTrivia())); + .WithAsyncKeyword(AsyncKeyword.WithPrependedLeadingTrivia(anonymous.GetLeadingTrivia())); } diff --git a/src/Analyzers/CSharp/CodeFixes/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs index 71ae670f8bc24..35a8ac00ac066 100644 --- a/src/Analyzers/CSharp/CodeFixes/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs @@ -14,6 +14,8 @@ namespace Microsoft.CodeAnalysis.CSharp.MakeMethodSynchronous; +using static CSharpSyntaxTokens; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.MakeMethodSynchronous), Shared] [ExtensionOrder(After = PredefinedCodeFixProviderNames.AddImport)] internal class CSharpMakeMethodSynchronousCodeFixProvider : AbstractMakeMethodSynchronousCodeFixProvider @@ -63,7 +65,7 @@ private static TypeSyntax FixMethodReturnType(IMethodSymbol methodSymbol, TypeSy if (returnType.OriginalDefinition.Equals(knownTypes.TaskType)) { // If the return type is Task, then make the new return type "void". - newReturnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)).WithTriviaFrom(returnTypeSyntax); + newReturnType = SyntaxFactory.PredefinedType(VoidKeyword).WithTriviaFrom(returnTypeSyntax); } else if (returnType.OriginalDefinition.Equals(knownTypes.TaskOfTType)) { diff --git a/src/Analyzers/CSharp/CodeFixes/MakeStatementAsynchronous/CSharpMakeStatementAsynchronousCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/MakeStatementAsynchronous/CSharpMakeStatementAsynchronousCodeFixProvider.cs index 5b08afa5ecbd9..c490d8e1d7355 100644 --- a/src/Analyzers/CSharp/CodeFixes/MakeStatementAsynchronous/CSharpMakeStatementAsynchronousCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/MakeStatementAsynchronous/CSharpMakeStatementAsynchronousCodeFixProvider.cs @@ -19,6 +19,9 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.MakeStatementAsynchronous; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.MakeStatementAsynchronous), Shared] internal class CSharpMakeStatementAsynchronousCodeFixProvider : SyntaxEditorBasedCodeFixProvider { @@ -72,22 +75,22 @@ private static void MakeStatementAsynchronous(SyntaxEditor editor, SyntaxNode st case ForEachStatementSyntax forEach: newStatement = forEach .WithForEachKeyword(forEach.ForEachKeyword.WithLeadingTrivia()) - .WithAwaitKeyword(SyntaxFactory.Token(SyntaxKind.AwaitKeyword).WithLeadingTrivia(forEach.GetLeadingTrivia())); + .WithAwaitKeyword(AwaitKeyword.WithLeadingTrivia(forEach.GetLeadingTrivia())); break; case ForEachVariableStatementSyntax forEachDeconstruction: newStatement = forEachDeconstruction .WithForEachKeyword(forEachDeconstruction.ForEachKeyword.WithLeadingTrivia()) - .WithAwaitKeyword(SyntaxFactory.Token(SyntaxKind.AwaitKeyword).WithLeadingTrivia(forEachDeconstruction.GetLeadingTrivia())); + .WithAwaitKeyword(AwaitKeyword.WithLeadingTrivia(forEachDeconstruction.GetLeadingTrivia())); break; case UsingStatementSyntax usingStatement: newStatement = usingStatement .WithUsingKeyword(usingStatement.UsingKeyword.WithLeadingTrivia()) - .WithAwaitKeyword(SyntaxFactory.Token(SyntaxKind.AwaitKeyword).WithLeadingTrivia(usingStatement.GetLeadingTrivia())); + .WithAwaitKeyword(AwaitKeyword.WithLeadingTrivia(usingStatement.GetLeadingTrivia())); break; case LocalDeclarationStatementSyntax localDeclaration: newStatement = localDeclaration .WithUsingKeyword(localDeclaration.UsingKeyword.WithLeadingTrivia()) - .WithAwaitKeyword(SyntaxFactory.Token(SyntaxKind.AwaitKeyword).WithLeadingTrivia(localDeclaration.GetLeadingTrivia())); + .WithAwaitKeyword(AwaitKeyword.WithLeadingTrivia(localDeclaration.GetLeadingTrivia())); break; default: return; diff --git a/src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs index c829b08232f00..f52e61fc4371b 100644 --- a/src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs @@ -117,7 +117,7 @@ private static ImmutableArray GetAllUsingDirectives(Compil Recurse(compilationUnit.Members); - return result.ToImmutable(); + return result.ToImmutableAndClear(); void Recurse(SyntaxList members) { diff --git a/src/Analyzers/CSharp/CodeFixes/NewLines/EmbeddedStatementPlacement/EmbeddedStatementPlacementCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/NewLines/EmbeddedStatementPlacement/EmbeddedStatementPlacementCodeFixProvider.cs index 3d54e0573d54d..6b05c0c12e76f 100644 --- a/src/Analyzers/CSharp/CodeFixes/NewLines/EmbeddedStatementPlacement/EmbeddedStatementPlacementCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/NewLines/EmbeddedStatementPlacement/EmbeddedStatementPlacementCodeFixProvider.cs @@ -14,12 +14,13 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.NewLines.EmbeddedStatementPlacement; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.EmbeddedStatementPlacement), Shared] internal sealed class EmbeddedStatementPlacementCodeFixProvider : CodeFixProvider { @@ -52,7 +53,7 @@ public static async Task FixAllAsync(Document document, ImmutableArray var options = await document.GetCSharpCodeFixOptionsProviderAsync(codeActionOptionsProvider, cancellationToken).ConfigureAwait(false); - var endOfLineTrivia = SyntaxFactory.ElasticEndOfLine(options.NewLine); + var endOfLineTrivia = ElasticEndOfLine(options.NewLine); foreach (var diagnostic in diagnostics) FixOne(editor, diagnostic, endOfLineTrivia, cancellationToken); @@ -95,7 +96,7 @@ private static void FixOne( blockSyntax.Statements.Count == 0) { updatedStatement = blockSyntax.WithCloseBraceToken( - AddLeadingTrivia(blockSyntax.CloseBraceToken, SyntaxFactory.ElasticMarker)); + AddLeadingTrivia(blockSyntax.CloseBraceToken, ElasticMarker)); } return updatedStatement; @@ -118,11 +119,11 @@ private static void FixOne( if (!EmbeddedStatementPlacementDiagnosticAnalyzer.ContainsEndOfLineBetween(previousToken, openBrace)) { currentBlock = currentBlock.WithOpenBraceToken( - AddLeadingTrivia(currentBlock.OpenBraceToken, SyntaxFactory.ElasticMarker)); + AddLeadingTrivia(currentBlock.OpenBraceToken, ElasticMarker)); } return currentBlock.WithCloseBraceToken( - AddLeadingTrivia(currentBlock.CloseBraceToken, SyntaxFactory.ElasticMarker)); + AddLeadingTrivia(currentBlock.CloseBraceToken, ElasticMarker)); }); } } diff --git a/src/Analyzers/CSharp/CodeFixes/RemoveAsyncModifier/CSharpRemoveAsyncModifierCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/RemoveAsyncModifier/CSharpRemoveAsyncModifierCodeFixProvider.cs index 5e29cca8ceb24..0a453ff0458ed 100644 --- a/src/Analyzers/CSharp/CodeFixes/RemoveAsyncModifier/CSharpRemoveAsyncModifierCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/RemoveAsyncModifier/CSharpRemoveAsyncModifierCodeFixProvider.cs @@ -15,6 +15,8 @@ namespace Microsoft.CodeAnalysis.CSharp.RemoveAsyncModifier; +using static CSharpSyntaxTokens; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveAsyncModifier), Shared] [ExtensionOrder(After = PredefinedCodeFixProviderNames.MakeMethodSynchronous)] internal partial class CSharpRemoveAsyncModifierCodeFixProvider : AbstractRemoveAsyncModifierCodeFixProvider @@ -34,7 +36,7 @@ protected override bool IsAsyncSupportingFunctionSyntax(SyntaxNode node) protected override SyntaxNode? ConvertToBlockBody(SyntaxNode node, ExpressionSyntax expressionBody) { - var semicolonToken = SyntaxFactory.Token(SyntaxKind.SemicolonToken); + var semicolonToken = SemicolonToken; if (expressionBody.TryConvertToStatement(semicolonToken, createReturnStatementForExpression: false, out var statement)) { var block = SyntaxFactory.Block(statement); diff --git a/src/Analyzers/CSharp/CodeFixes/RemoveUnusedParametersAndValues/CSharpRemoveUnusedValuesCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/RemoveUnusedParametersAndValues/CSharpRemoveUnusedValuesCodeFixProvider.cs index 703e7c6d2210d..d80b9b34b4eaa 100644 --- a/src/Analyzers/CSharp/CodeFixes/RemoveUnusedParametersAndValues/CSharpRemoveUnusedValuesCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/RemoveUnusedParametersAndValues/CSharpRemoveUnusedValuesCodeFixProvider.cs @@ -21,6 +21,8 @@ namespace Microsoft.CodeAnalysis.CSharp.RemoveUnusedParametersAndValues; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnusedValues), Shared] [ExtensionOrder(After = PredefinedCodeFixProviderNames.AddImport)] internal class CSharpRemoveUnusedValuesCodeFixProvider : @@ -38,7 +40,7 @@ protected override ISyntaxFormatting GetSyntaxFormatting() => CSharpSyntaxFormatting.Instance; protected override BlockSyntax WrapWithBlockIfNecessary(IEnumerable statements) - => SyntaxFactory.Block(statements); + => Block(statements); protected override SyntaxToken GetForEachStatementIdentifier(ForEachStatementSyntax node) => node.Identifier; @@ -63,7 +65,7 @@ protected override SyntaxNode TryUpdateNameForFlaggedNode(SyntaxNode node, Synta // If we are generating a discard on the left of an initialization with an implicit object creation on the right, // then we need to replace the implicit object creation with an explicit one. // For example: 'TypeName v = new();' must be changed to '_ = new TypeName();' - var objectCreationNode = SyntaxFactory.ObjectCreationExpression( + var objectCreationNode = ObjectCreationExpression( newKeyword: implicitObjectCreation.NewKeyword, type: parent.Type, argumentList: implicitObjectCreation.ArgumentList, @@ -75,8 +77,8 @@ protected override SyntaxNode TryUpdateNameForFlaggedNode(SyntaxNode node, Synta case SyntaxKind.SingleVariableDesignation: return newName.ValueText == AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.DiscardVariableName - ? SyntaxFactory.DiscardDesignation().WithTriviaFrom(node) - : SyntaxFactory.SingleVariableDesignation(newName).WithTriviaFrom(node); + ? DiscardDesignation().WithTriviaFrom(node) + : SingleVariableDesignation(newName).WithTriviaFrom(node); case SyntaxKind.CatchDeclaration: var catchDeclaration = (CatchDeclarationSyntax)node; @@ -84,8 +86,8 @@ protected override SyntaxNode TryUpdateNameForFlaggedNode(SyntaxNode node, Synta case SyntaxKind.VarPattern: return node.IsParentKind(SyntaxKind.Subpattern) - ? SyntaxFactory.DiscardPattern().WithTriviaFrom(node) - : SyntaxFactory.DiscardDesignation(); + ? DiscardPattern().WithTriviaFrom(node) + : DiscardDesignation(); default: Debug.Fail($"Unexpected node kind for local/parameter declaration or reference: '{node.Kind()}'"); @@ -105,7 +107,7 @@ protected override SyntaxNode TryUpdateParentOfUpdatedNode(SyntaxNode parent, Sy parent.SyntaxTree.Options.LanguageVersion() >= LanguageVersion.CSharp9) { var trailingTrivia = declarationPattern.Type.GetTrailingTrivia().AddRange(triviaToAppend); - return SyntaxFactory.TypePattern(declarationPattern.Type).WithTrailingTrivia(trailingTrivia); + return TypePattern(declarationPattern.Type).WithTrailingTrivia(trailingTrivia); } // 1) `... is { } variable` -> `... is { }` @@ -132,7 +134,7 @@ assignment.Right is ImplicitObjectCreationExpressionSyntax implicitObjectCreatio // If we are generating a discard on the left of an assignment with an implicit object creation on the right, // then we need to replace the implicit object creation with an explicit one. // For example: 'v = new();' must be changed to '_ = new TypeOfV();' - var objectCreationNode = SyntaxFactory.ObjectCreationExpression( + var objectCreationNode = ObjectCreationExpression( newKeyword: implicitObjectCreation.NewKeyword, type: type.GenerateTypeSyntax(allowVar: false), argumentList: implicitObjectCreation.ArgumentList, @@ -227,7 +229,7 @@ protected override SyntaxNode GetReplacementNodeForCompoundAssignment( // Remove leading trivia from 'leftOfAssignment' as it should have been moved to 'newAssignmentTarget'. leftOfAssignment = leftOfAssignment.WithoutLeadingTrivia(); return editor.Generator.AssignmentStatement(newAssignmentTarget, - SyntaxFactory.BinaryExpression(SyntaxKind.CoalesceExpression, leftOfAssignment, rightOfAssignment)); + BinaryExpression(SyntaxKind.CoalesceExpression, leftOfAssignment, rightOfAssignment)); } } else @@ -239,7 +241,7 @@ protected override SyntaxNode GetReplacementNodeForCompoundAssignment( return originalCompoundAssignment; } - return SyntaxFactory.BinaryExpression(mappedBinaryExpressionKind, leftOfAssignment, rightOfAssignment); + return BinaryExpression(mappedBinaryExpressionKind, leftOfAssignment, rightOfAssignment); } } diff --git a/src/Analyzers/CSharp/CodeFixes/UseCollectionExpression/CSharpCollectionExpressionRewriter.cs b/src/Analyzers/CSharp/CodeFixes/UseCollectionExpression/CSharpCollectionExpressionRewriter.cs index 25e1b7fcc3468..a2fc8b8eed3d1 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseCollectionExpression/CSharpCollectionExpressionRewriter.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseCollectionExpression/CSharpCollectionExpressionRewriter.cs @@ -20,6 +20,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseCollectionExpression; +using static CSharpSyntaxTokens; using static SyntaxFactory; internal static class CSharpCollectionExpressionRewriter @@ -117,9 +118,9 @@ CollectionExpressionSyntax CreateCollectionExpressionWithoutExistingElements() var nullTokenAnnotation = new SyntaxAnnotation(); var initializer = InitializerExpression( SyntaxKind.CollectionInitializerExpression, - Token(SyntaxKind.OpenBraceToken).WithAdditionalAnnotations(openBraceTokenAnnotation), - [LiteralExpression(SyntaxKind.NullLiteralExpression, Token(SyntaxKind.NullKeyword).WithAdditionalAnnotations(nullTokenAnnotation))], - Token(SyntaxKind.CloseBraceToken)); + OpenBraceToken.WithAdditionalAnnotations(openBraceTokenAnnotation), + [LiteralExpression(SyntaxKind.NullLiteralExpression, NullKeyword.WithAdditionalAnnotations(nullTokenAnnotation))], + CloseBraceToken); // Update the doc with the new object (now with initializer). var updatedRoot = document.Root.ReplaceNode( @@ -146,9 +147,9 @@ CollectionExpressionSyntax CreateCollectionExpressionWithoutExistingElements() // Make the collection expression with the braces on new lines, at the desired brace indentation. var finalCollection = CollectionExpression( - Token(SyntaxKind.OpenBracketToken).WithLeadingTrivia(endOfLine, Whitespace(openBraceIndentation)).WithTrailingTrivia(endOfLine), + OpenBracketToken.WithLeadingTrivia(endOfLine, Whitespace(openBraceIndentation)).WithTrailingTrivia(endOfLine), SeparatedList(nodesAndTokens), - Token(SyntaxKind.CloseBracketToken).WithLeadingTrivia(Whitespace(openBraceIndentation))); + CloseBracketToken.WithLeadingTrivia(Whitespace(openBraceIndentation))); // Now, figure out what trivia to move over from the original object over to the new collection. return UseCollectionExpressionHelpers.ReplaceWithCollectionExpression( @@ -167,9 +168,9 @@ CollectionExpressionSyntax CreateCollectionExpressionWithoutExistingElements() nodesAndTokens[^1] = RemoveTrailingWhitespace(nodesAndTokens[^1]); var collectionExpression = CollectionExpression( - Token(SyntaxKind.OpenBracketToken).WithoutTrivia(), + OpenBracketToken.WithoutTrivia(), SeparatedList(nodesAndTokens), - Token(SyntaxKind.CloseBracketToken).WithoutTrivia()); + CloseBracketToken.WithoutTrivia()); return collectionExpression.WithTriviaFrom(expressionToReplace); } } @@ -337,7 +338,7 @@ void AddCommaIfMissing(bool last) nodesAndTokens[^1] = lastNode.WithTrailingTrivia(lastNode.GetTrailingTrivia().Where(t => !trailingWhitespaceAndComments.Contains(t))); - var commaToken = Token(SyntaxKind.CommaToken) + var commaToken = CommaToken .WithoutLeadingTrivia() .WithTrailingTrivia(TriviaList(trailingWhitespaceAndComments).AddRange(triviaAfterComma)); @@ -403,7 +404,7 @@ static CollectionElementSyntax CreateCollectionElement( { return useSpread ? SpreadElement( - Token(SyntaxKind.DotDotToken).WithLeadingTrivia(expression.GetLeadingTrivia()).WithTrailingTrivia(Space), + DotDotToken.WithLeadingTrivia(expression.GetLeadingTrivia()).WithTrailingTrivia(Space), expression.WithoutLeadingTrivia()) : ExpressionElement(expression); } diff --git a/src/Analyzers/CSharp/CodeFixes/UseCollectionExpression/CSharpUseCollectionExpressionForFluentCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseCollectionExpression/CSharpUseCollectionExpressionForFluentCodeFixProvider.cs index 00d7b1232c2a7..fab7a804b6b76 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseCollectionExpression/CSharpUseCollectionExpressionForFluentCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseCollectionExpression/CSharpUseCollectionExpressionForFluentCodeFixProvider.cs @@ -27,6 +27,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseCollectionExpression; using static CSharpCollectionExpressionRewriter; using static CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer; +using static CSharpSyntaxTokens; using static SyntaxFactory; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseCollectionExpressionForFluent), Shared] @@ -176,7 +177,7 @@ static async Task> GetArgumentsAsync( } else { - nodesAndTokens.Add(Token(SyntaxKind.CommaToken).WithoutTrivia()); + nodesAndTokens.Add(CommaToken.WithoutTrivia()); AddOriginallyFirstArgument(argument); } } diff --git a/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider_CollectionInitializer.cs b/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider_CollectionInitializer.cs index 4fd9591138b93..f1d0323603ec6 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider_CollectionInitializer.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider_CollectionInitializer.cs @@ -16,6 +16,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseCollectionInitializer; +using static CSharpSyntaxTokens; using static SyntaxFactory; internal partial class CSharpUseCollectionInitializerCodeFixProvider @@ -56,7 +57,7 @@ SeparatedSyntaxList CreateCollectionInitializerExpressions() if (i < matches.Length - 1) { nodesAndTokens.Add(expression); - nodesAndTokens.Add(Token(SyntaxKind.CommaToken).WithTrailingTrivia(trailingTrivia)); + nodesAndTokens.Add(CommaToken.WithTrailingTrivia(trailingTrivia)); } else { @@ -131,11 +132,11 @@ static ExpressionSyntax ConvertInvocation(InvocationExpressionSyntax invocation) { return InitializerExpression( SyntaxKind.ComplexElementInitializerExpression, - Token(SyntaxKind.OpenBraceToken).WithoutTrivia(), + OpenBraceToken.WithoutTrivia(), SeparatedList( arguments.Select(a => a.Expression), arguments.GetSeparators()), - Token(SyntaxKind.CloseBraceToken).WithoutTrivia()); + CloseBraceToken.WithoutTrivia()); } } } diff --git a/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundCoalesceAssignmentCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundCoalesceAssignmentCodeFixProvider.cs index d35d0cdf512cf..c7a2a8eb775a9 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundCoalesceAssignmentCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundCoalesceAssignmentCodeFixProvider.cs @@ -21,6 +21,9 @@ namespace Microsoft.CodeAnalysis.CSharp.UseCompoundAssignment; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseCompoundCoalesceAssignment), Shared] internal class CSharpUseCompoundCoalesceAssignmentCodeFixProvider : SyntaxEditorBasedCodeFixProvider { @@ -59,10 +62,10 @@ protected override async Task FixAllAsync( // we have `if (x is null) x = y;`. Update `x = y` to be `x ??= y`, then replace the entire // if-statement with that assignment statement. - var newAssignment = SyntaxFactory.AssignmentExpression( + var newAssignment = AssignmentExpression( SyntaxKind.CoalesceAssignmentExpression, assignment.Left, - SyntaxFactory.Token(SyntaxKind.QuestionQuestionEqualsToken).WithTriviaFrom(assignment.OperatorToken), + QuestionQuestionEqualsToken.WithTriviaFrom(assignment.OperatorToken), assignment.Right).WithTriviaFrom(assignment); var newWhenTrueStatement = whenTrueStatement.ReplaceNode(assignment, newAssignment); @@ -105,8 +108,8 @@ protected override async Task FixAllAsync( var coalesceRight = (ParenthesizedExpressionSyntax)currentCoalesce.Right; var assignment = (AssignmentExpressionSyntax)coalesceRight.Expression; - var compoundOperator = SyntaxFactory.Token(SyntaxKind.QuestionQuestionEqualsToken); - var finalAssignment = SyntaxFactory.AssignmentExpression( + var compoundOperator = QuestionQuestionEqualsToken; + var finalAssignment = AssignmentExpression( SyntaxKind.CoalesceAssignmentExpression, assignment.Left, compoundOperator.WithTriviaFrom(assignment.OperatorToken), diff --git a/src/Analyzers/CSharp/CodeFixes/UseDeconstruction/CSharpUseDeconstructionCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseDeconstruction/CSharpUseDeconstructionCodeFixProvider.cs index 388f25d9c52c8..7a6408f8eefbe 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseDeconstruction/CSharpUseDeconstructionCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseDeconstruction/CSharpUseDeconstructionCodeFixProvider.cs @@ -21,6 +21,8 @@ namespace Microsoft.CodeAnalysis.CSharp.UseDeconstruction; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseDeconstruction), Shared] internal sealed class CSharpUseDeconstructionCodeFixProvider : SyntaxEditorBasedCodeFixProvider { @@ -121,7 +123,7 @@ private ForEachVariableStatementSyntax CreateForEachVariableStatement(INamedType // Copy all the tokens/nodes from the existing foreach statement to the new foreach statement. // However, convert the existing declaration over to a "var (x, y)" declaration or (int x, int y) // tuple expression. - return SyntaxFactory.ForEachVariableStatement( + return ForEachVariableStatement( forEachStatement.AttributeLists, forEachStatement.AwaitKeyword, forEachStatement.ForEachKeyword, @@ -139,8 +141,8 @@ private ExpressionStatementSyntax CreateDeconstructionStatement( // Copy all the tokens/nodes from the existing declaration statement to the new assignment // statement. However, convert the existing declaration over to a "var (x, y)" declaration // or (int x, int y) tuple expression. - return SyntaxFactory.ExpressionStatement( - SyntaxFactory.AssignmentExpression( + return ExpressionStatement( + AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, CreateTupleOrDeclarationExpression(tupleType, declarationStatement.Declaration.Type), variableDeclarator.Initializer.EqualsToken, @@ -160,15 +162,15 @@ private ExpressionSyntax CreateTupleOrDeclarationExpression(INamedTypeSymbol tup } private static DeclarationExpressionSyntax CreateDeclarationExpression(INamedTypeSymbol tupleType, TypeSyntax typeNode) - => SyntaxFactory.DeclarationExpression( - typeNode, SyntaxFactory.ParenthesizedVariableDesignation( + => DeclarationExpression( + typeNode, ParenthesizedVariableDesignation( [.. tupleType.TupleElements.Select( - e => SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier(e.Name.EscapeIdentifier())))])); + e => SingleVariableDesignation(Identifier(e.Name.EscapeIdentifier())))])); private TupleExpressionSyntax CreateTupleExpression(TupleTypeSyntax typeNode) - => SyntaxFactory.TupleExpression( + => TupleExpression( typeNode.OpenParenToken, - SyntaxFactory.SeparatedList(new SyntaxNodeOrTokenList(typeNode.Elements.GetWithSeparators().Select(ConvertTupleTypeElementComponent))), + SeparatedList(new SyntaxNodeOrTokenList(typeNode.Elements.GetWithSeparators().Select(ConvertTupleTypeElementComponent))), typeNode.CloseParenToken); private SyntaxNodeOrToken ConvertTupleTypeElementComponent(SyntaxNodeOrToken nodeOrToken) @@ -182,9 +184,9 @@ private SyntaxNodeOrToken ConvertTupleTypeElementComponent(SyntaxNodeOrToken nod // "int x" as a tuple element directly translates to "int x" (a declaration expression // with a variable designation 'x'). var node = (TupleElementSyntax)nodeOrToken.AsNode(); - return SyntaxFactory.Argument( - SyntaxFactory.DeclarationExpression( + return Argument( + DeclarationExpression( node.Type, - SyntaxFactory.SingleVariableDesignation(node.Identifier))); + SingleVariableDesignation(node.Identifier))); } } diff --git a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs index 1eed151ee7e16..d0f4a86e388dc 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs @@ -14,6 +14,9 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.UseExpressionBodyForLambda; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class UseExpressionBodyForLambdaCodeActionHelpers { internal static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration, CancellationToken cancellationToken) @@ -43,7 +46,7 @@ private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax if (declaration.ArrowToken.TrailingTrivia.All(t => t.IsWhitespaceOrEndOfLine()) && expressionBody.GetLeadingTrivia().All(t => t.IsWhitespaceOrEndOfLine())) { - updatedDecl = updatedDecl.WithArrowToken(updatedDecl.ArrowToken.WithTrailingTrivia(SyntaxFactory.ElasticSpace)); + updatedDecl = updatedDecl.WithArrowToken(updatedDecl.ArrowToken.WithTrailingTrivia(ElasticSpace)); } return updatedDecl; @@ -66,10 +69,10 @@ private static LambdaExpressionSyntax WithBlockBody( // If the user is converting to a block, it's likely they intend to add multiple // statements to it. So make a multi-line block so that things are formatted properly // for them to do so. - return currentDeclaration.WithBody(SyntaxFactory.Block( - SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed), + return currentDeclaration.WithBody(Block( + OpenBraceToken.WithAppendedTrailingTrivia(ElasticCarriageReturnLineFeed), [statement], - SyntaxFactory.Token(SyntaxKind.CloseBraceToken))); + CloseBraceToken)); } private static bool CreateReturnStatementForExpression( diff --git a/src/Analyzers/CSharp/CodeFixes/UseImplicitOrExplicitType/UseExplicitTypeCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseImplicitOrExplicitType/UseExplicitTypeCodeFixProvider.cs index 81e2b0a3a5647..564251e4178d1 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseImplicitOrExplicitType/UseExplicitTypeCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseImplicitOrExplicitType/UseExplicitTypeCodeFixProvider.cs @@ -22,6 +22,9 @@ namespace Microsoft.CodeAnalysis.CSharp.TypeStyle; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseExplicitType), Shared] internal class UseExplicitTypeCodeFixProvider : SyntaxEditorBasedCodeFixProvider { @@ -182,7 +185,7 @@ private static ExpressionSyntax GenerateTupleDeclaration(ITypeSymbol typeSymbol, case SyntaxKind.SingleVariableDesignation: case SyntaxKind.DiscardDesignation: var typeName = type.GenerateTypeSyntax(allowVar: false); - newDeclaration = SyntaxFactory.DeclarationExpression(typeName, designation); + newDeclaration = DeclarationExpression(typeName, designation); break; case SyntaxKind.ParenthesizedVariableDesignation: newDeclaration = GenerateTupleDeclaration(type, (ParenthesizedVariableDesignationSyntax)designation); @@ -195,15 +198,15 @@ private static ExpressionSyntax GenerateTupleDeclaration(ITypeSymbol typeSymbol, .WithLeadingTrivia(designation.GetAllPrecedingTriviaToPreviousToken()) .WithTrailingTrivia(designation.GetTrailingTrivia()); - builder.Add(SyntaxFactory.Argument(newDeclaration)); + builder.Add(Argument(newDeclaration)); } - var separatorBuilder = ArrayBuilder.GetInstance(builder.Count - 1, SyntaxFactory.Token(leading: default, SyntaxKind.CommaToken, trailing: default)); + var separatorBuilder = ArrayBuilder.GetInstance(builder.Count - 1, Token(leading: default, SyntaxKind.CommaToken, trailing: default)); - return SyntaxFactory.TupleExpression( - SyntaxFactory.Token(SyntaxKind.OpenParenToken).WithTrailingTrivia(), - SyntaxFactory.SeparatedList(builder.ToImmutable(), separatorBuilder.ToImmutableAndFree()), - SyntaxFactory.Token(SyntaxKind.CloseParenToken)) + return TupleExpression( + OpenParenToken.WithTrailingTrivia(), + SeparatedList(builder, separatorBuilder), + CloseParenToken) .WithTrailingTrivia(parensDesignation.GetTrailingTrivia()); } diff --git a/src/Analyzers/CSharp/CodeFixes/UseIndexOrRangeOperator/CSharpUseRangeOperatorCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseIndexOrRangeOperator/CSharpUseRangeOperatorCodeFixProvider.cs index c038856ac9110..77bd5c04a6d73 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseIndexOrRangeOperator/CSharpUseRangeOperatorCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseIndexOrRangeOperator/CSharpUseRangeOperatorCodeFixProvider.cs @@ -25,6 +25,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator; using static CodeFixHelpers; using static CSharpUseRangeOperatorDiagnosticAnalyzer; +using static CSharpSyntaxTokens; using static Helpers; using static SyntaxFactory; @@ -96,9 +97,9 @@ private static ExpressionSyntax FixOne(Result result, SyntaxGenerator generator) { var argList = invocation.ArgumentList; var argumentList = BracketedArgumentList( - Token(SyntaxKind.OpenBracketToken).WithTriviaFrom(argList.OpenParenToken), + OpenBracketToken.WithTriviaFrom(argList.OpenParenToken), arguments, - Token(SyntaxKind.CloseBracketToken).WithTriviaFrom(argList.CloseParenToken)); + CloseBracketToken.WithTriviaFrom(argList.CloseParenToken)); if (invocation.Expression is MemberBindingExpressionSyntax) { // x?.Substring(...) -> x?[...] diff --git a/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForCastAndEqualityOperatorCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForCastAndEqualityOperatorCodeFixProvider.cs index cd466f264297b..22a8b3688ef38 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForCastAndEqualityOperatorCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForCastAndEqualityOperatorCodeFixProvider.cs @@ -20,6 +20,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseIsNullCheck; +using static CSharpSyntaxTokens; using static SyntaxFactory; using static UseIsNullCheckHelpers; @@ -91,7 +92,7 @@ private static ExpressionSyntax Rewrite(BinaryExpressionSyntax binary) return BinaryExpression( SyntaxKind.IsExpression, isPattern.Expression, - PredefinedType(Token(SyntaxKind.ObjectKeyword))).WithTriviaFrom(isPattern); + PredefinedType(ObjectKeyword)).WithTriviaFrom(isPattern); } } @@ -106,7 +107,7 @@ private static IsPatternExpressionSyntax Rewrite( var castExpr = (CastExpressionSyntax)expr; return IsPatternExpression( castExpr.Expression.WithTriviaFrom(binary.Left), - Token(SyntaxKind.IsKeyword).WithTriviaFrom(binary.OperatorToken), + IsKeyword.WithTriviaFrom(binary.OperatorToken), ConstantPattern(nullLiteral).WithTriviaFrom(binary.Right)); } } diff --git a/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForReferenceEqualsCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForReferenceEqualsCodeFixProvider.cs index 66be4c4f6f14e..94f65c24eb81a 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForReferenceEqualsCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForReferenceEqualsCodeFixProvider.cs @@ -13,6 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseIsNullCheck; +using static CSharpSyntaxTokens; using static SyntaxFactory; using static UseIsNullCheckHelpers; @@ -48,7 +49,7 @@ private static SyntaxNode CreateIsNotNullCheck(ExpressionSyntax argument) return IsPatternExpression( argument, UnaryPattern( - Token(SyntaxKind.NotKeyword), + NotKeyword, s_nullLiteralPattern)).Parenthesize(); } else @@ -56,7 +57,7 @@ private static SyntaxNode CreateIsNotNullCheck(ExpressionSyntax argument) return BinaryExpression( SyntaxKind.IsExpression, argument, - PredefinedType(Token(SyntaxKind.ObjectKeyword))).Parenthesize(); + PredefinedType(ObjectKeyword)).Parenthesize(); } } diff --git a/src/Analyzers/CSharp/CodeFixes/UseLocalFunction/CSharpUseLocalFunctionCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseLocalFunction/CSharpUseLocalFunctionCodeFixProvider.cs index d49db09283500..90ffa1fef5fb2 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseLocalFunction/CSharpUseLocalFunctionCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseLocalFunction/CSharpUseLocalFunctionCodeFixProvider.cs @@ -16,7 +16,6 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.CodeGeneration; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -28,10 +27,13 @@ namespace Microsoft.CodeAnalysis.CSharp.UseLocalFunction; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseLocalFunction), Shared] internal class CSharpUseLocalFunctionCodeFixProvider : SyntaxEditorBasedCodeFixProvider { - private static readonly TypeSyntax s_objectType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + private static readonly TypeSyntax s_objectType = PredefinedType(ObjectKeyword); [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] @@ -212,7 +214,7 @@ private static LocalFunctionStatementSyntax CreateLocalFunctionStatement( var modifiers = new SyntaxTokenList(); if (makeStatic) { - modifiers = modifiers.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + modifiers = modifiers.Add(StaticKeyword); } if (anonymousFunction.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword)) @@ -232,14 +234,14 @@ private static LocalFunctionStatementSyntax CreateLocalFunctionStatement( : null; var expressionBody = anonymousFunction.Body is ExpressionSyntax expression - ? SyntaxFactory.ArrowExpressionClause(((LambdaExpressionSyntax)anonymousFunction).ArrowToken, expression) + ? ArrowExpressionClause(((LambdaExpressionSyntax)anonymousFunction).ArrowToken, expression) : null; var semicolonToken = anonymousFunction.Body is ExpressionSyntax ? localDeclaration.SemicolonToken : default; - return SyntaxFactory.LocalFunctionStatement( + return LocalFunctionStatement( modifiers, returnType, identifier, typeParameterList, parameterList, constraintClauses, body, expressionBody, semicolonToken); } @@ -252,8 +254,8 @@ private static ParameterListSyntax GenerateParameterList( return parameterList != null ? parameterList.ReplaceNodes(parameterList.Parameters, (parameterNode, _) => PromoteParameter(generator, parameterNode, delegateMethod.Parameters.ElementAtOrDefault(i++))) - : SyntaxFactory.ParameterList([.. delegateMethod.Parameters.Select(parameter => - PromoteParameter(generator, SyntaxFactory.Parameter(parameter.Name.ToIdentifierToken()), parameter))]); + : ParameterList([.. delegateMethod.Parameters.Select(parameter => + PromoteParameter(generator, Parameter(parameter.Name.ToIdentifierToken()), parameter))]); static ParameterSyntax PromoteParameter(SyntaxGenerator generator, ParameterSyntax parameterNode, IParameterSymbol delegateParameter) { @@ -279,7 +281,7 @@ private static ParameterListSyntax TryGetOrCreateParameterList(AnonymousFunction switch (anonymousFunction) { case SimpleLambdaExpressionSyntax simpleLambda: - return SyntaxFactory.ParameterList([simpleLambda.Parameter]); + return ParameterList([simpleLambda.Parameter]); case ParenthesizedLambdaExpressionSyntax parenthesizedLambda: return parenthesizedLambda.ParameterList; case AnonymousMethodExpressionSyntax anonymousMethod: @@ -310,7 +312,7 @@ private static InvocationExpressionSyntax WithNewParameterNames(InvocationExpres return argumentNode; } - return argumentNode.WithNameColon(argumentNode.NameColon.WithName(SyntaxFactory.IdentifierName(newParameter.Identifier))); + return argumentNode.WithNameColon(argumentNode.NameColon.WithName(IdentifierName(newParameter.Identifier))); }); } @@ -321,5 +323,5 @@ private static int TryDetermineParameterIndex(NameColonSyntax argumentNameColon, } private static EqualsValueClauseSyntax GetDefaultValue(SyntaxGenerator generator, IParameterSymbol parameter) - => SyntaxFactory.EqualsValueClause(ExpressionGenerator.GenerateExpression(generator, parameter.Type, parameter.ExplicitDefaultValue, canUseFieldReference: true)); + => EqualsValueClause(ExpressionGenerator.GenerateExpression(generator, parameter.Type, parameter.ExplicitDefaultValue, canUseFieldReference: true)); } diff --git a/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs index ef89b432b9a55..c096ad0701ea7 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs @@ -13,6 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseObjectInitializer; +using static CSharpSyntaxTokens; using ObjectInitializerMatch = Match; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseObjectInitializer), Shared] @@ -78,10 +79,7 @@ private static SeparatedSyntaxList CreateExpressions( if (i < matches.Length - 1) { nodesAndTokens.Add(newAssignment); - var commaToken = SyntaxFactory.Token(SyntaxKind.CommaToken) - .WithTriviaFrom(expressionStatement.SemicolonToken); - - nodesAndTokens.Add(commaToken); + nodesAndTokens.Add(CommaToken.WithTriviaFrom(expressionStatement.SemicolonToken)); } else { diff --git a/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/UseInitializerHelpers.cs b/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/UseInitializerHelpers.cs index f083978fdb53b..e556f68855d05 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/UseInitializerHelpers.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/UseInitializerHelpers.cs @@ -8,6 +8,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseObjectInitializer; +using static CSharpSyntaxTokens; using static SyntaxFactory; internal static class UseInitializerHelpers @@ -57,7 +58,7 @@ public static void AddExistingItems( var last = nodesAndTokens.Last(); nodesAndTokens.RemoveLast(); nodesAndTokens.Add(last.WithTrailingTrivia()); - nodesAndTokens.Add(Token(SyntaxKind.CommaToken).WithTrailingTrivia(last.GetTrailingTrivia())); + nodesAndTokens.Add(CommaToken.WithTrailingTrivia(last.GetTrailingTrivia())); } } } diff --git a/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndMemberAccessCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndMemberAccessCodeFixProvider.cs index 2fae3cf5660b0..6e3d3eed462ae 100644 --- a/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndMemberAccessCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndMemberAccessCodeFixProvider.cs @@ -20,6 +20,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UsePatternMatching; +using static CSharpSyntaxTokens; using static SyntaxFactory; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UsePatternMatchingAsAndMemberAccess), Shared] @@ -67,11 +68,11 @@ private static void FixOne(SyntaxEditor editor, Diagnostic diagnostic, Cancellat // { X.Y: pattern } var propertyPattern = PropertyPatternClause( - Token(SyntaxKind.OpenBraceToken).WithoutTrivia().WithAppendedTrailingTrivia(Space), + OpenBraceToken.WithoutTrivia().WithAppendedTrailingTrivia(Space), [Subpattern( CreateExpressionColon(conditionalAccessExpression), CreatePattern(binaryExpression, isPatternExpression).WithTrailingTrivia(Space))], - Token(SyntaxKind.CloseBraceToken).WithoutTrivia()); + CloseBraceToken.WithoutTrivia()); // T { X.Y: pattern } var newPattern = RecursivePattern( @@ -83,7 +84,7 @@ private static void FixOne(SyntaxEditor editor, Diagnostic diagnostic, Cancellat // is T { X.Y: pattern } var newIsExpression = IsPatternExpression( asExpression.Left, - Token(SyntaxKind.IsKeyword).WithTriviaFrom(asExpression.OperatorToken), + IsKeyword.WithTriviaFrom(asExpression.OperatorToken), newPattern); var toReplace = parent.WalkUpParentheses(); @@ -100,7 +101,7 @@ static BaseExpressionColonSyntax CreateExpressionColon(ConditionalAccessExpressi if (whenNotNull is MemberBindingExpressionSyntax { Name: IdentifierNameSyntax identifierName }) return NameColon(identifierName); - return ExpressionColon(RewriteMemberBindingToExpression(whenNotNull), Token(SyntaxKind.ColonToken)); + return ExpressionColon(RewriteMemberBindingToExpression(whenNotNull), ColonToken); } static ExpressionSyntax RewriteMemberBindingToExpression(ExpressionSyntax expression) diff --git a/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndNullCheckCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndNullCheckCodeFixProvider.cs index a9fd0a6291559..c58d7853b87b2 100644 --- a/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndNullCheckCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpAsAndNullCheckCodeFixProvider.cs @@ -20,6 +20,9 @@ namespace Microsoft.CodeAnalysis.CSharp.UsePatternMatching; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UsePatternMatchingAsAndNullCheck), Shared] internal partial class CSharpAsAndNullCheckCodeFixProvider : SyntaxEditorBasedCodeFixProvider { @@ -103,9 +106,9 @@ private static void AddEdits( var newIdentifier = declarator.Identifier .WithoutTrivia().WithTrailingTrivia(rightSideOfComparison.GetTrailingTrivia()); - var declarationPattern = SyntaxFactory.DeclarationPattern( - GetPatternType().WithoutTrivia().WithTrailingTrivia(SyntaxFactory.ElasticMarker), - SyntaxFactory.SingleVariableDesignation(newIdentifier)); + var declarationPattern = DeclarationPattern( + GetPatternType().WithoutTrivia().WithTrailingTrivia(ElasticMarker), + SingleVariableDesignation(newIdentifier)); var condition = GetCondition(languageVersion, comparison, asExpression, declarationPattern); @@ -165,7 +168,7 @@ private static ExpressionSyntax GetCondition( BinaryExpressionSyntax asExpression, DeclarationPatternSyntax declarationPattern) { - var isPatternExpression = SyntaxFactory.IsPatternExpression(asExpression.Left, declarationPattern); + var isPatternExpression = IsPatternExpression(asExpression.Left, declarationPattern); // We should negate the is-expression if we have something like "x == null" or "x is null" if (comparison.Kind() is not (SyntaxKind.EqualsExpression or SyntaxKind.IsPatternExpression)) @@ -175,10 +178,10 @@ private static ExpressionSyntax GetCondition( { // In C# 9 and higher, convert to `x is not string s`. return isPatternExpression.WithPattern( - SyntaxFactory.UnaryPattern(SyntaxFactory.Token(SyntaxKind.NotKeyword), isPatternExpression.Pattern)); + UnaryPattern(NotKeyword, isPatternExpression.Pattern)); } // In C# 8 and lower, convert to `!(x is string s)` - return SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, isPatternExpression.Parenthesize()); + return PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, isPatternExpression.Parenthesize()); } } diff --git a/src/Analyzers/CSharp/CodeFixes/UsePrimaryConstructor/CSharpUsePrimaryConstructorCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UsePrimaryConstructor/CSharpUsePrimaryConstructorCodeFixProvider.cs index 5ff90d64cbd3a..270355ef1cade 100644 --- a/src/Analyzers/CSharp/CodeFixes/UsePrimaryConstructor/CSharpUsePrimaryConstructorCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UsePrimaryConstructor/CSharpUsePrimaryConstructorCodeFixProvider.cs @@ -28,6 +28,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UsePrimaryConstructor; using static CSharpUsePrimaryConstructorDiagnosticAnalyzer; +using static CSharpSyntaxTokens; using static SyntaxFactory; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UsePrimaryConstructor), Shared] @@ -151,7 +152,7 @@ private static async Task UsePrimaryConstructorAsync( var finalAttributeLists = currentTypeDeclaration.AttributeLists.AddRange( constructorDeclaration.AttributeLists.Select( - a => a.WithTarget(AttributeTargetSpecifier(Token(SyntaxKind.MethodKeyword))).WithoutTrivia().WithAdditionalAnnotations(Formatter.Annotation))); + a => a.WithTarget(AttributeTargetSpecifier(MethodKeyword)).WithoutTrivia().WithAdditionalAnnotations(Formatter.Annotation))); var finalTrivia = CreateFinalTypeDeclarationLeadingTrivia( currentTypeDeclaration, constructorDeclaration, constructor, properties, removedMembers); @@ -238,38 +239,49 @@ ParameterListSyntax UpdateReferencesToNestedMembers(ParameterListSyntax paramete parameterList.DescendantNodes().OfType(), (nameSyntax, currentNameSyntax) => { - // Don't have to update if the member is already qualified. - - if (nameSyntax.Parent is not QualifiedNameSyntax qualifiedNameSyntax || - qualifiedNameSyntax.Left == nameSyntax) + if (nameSyntax.Parent is QualifiedNameSyntax qualifiedNameSyntax) { - // Qualified names occur in things like the `type` portion of the parameter - - // reference to a nested type in an unqualified fashion. Have to qualify this. - var symbol = semanticModel.GetSymbolInfo(nameSyntax, cancellationToken).GetAnySymbol(); - if (symbol is INamedTypeSymbol { ContainingType: { } containingType }) - return CreateDottedName(nameSyntax, currentNameSyntax, containingType); + // Don't have to update if the name is already the RHS of some qualified name. + if (qualifiedNameSyntax.Left == nameSyntax) + { + // Qualified names occur in things like the `type` portion of the parameter + return TryQualify(nameSyntax, currentNameSyntax); + } } - - if (nameSyntax.Parent is not MemberAccessExpressionSyntax memberAccessExpression || - memberAccessExpression.Expression == nameSyntax) + else if (nameSyntax.Parent is MemberAccessExpressionSyntax memberAccessExpression) { - // Member access expressions occur in things like the default initializer, or attribute - // arguments of the parameter. - - var symbol = semanticModel.GetSymbolInfo(nameSyntax, cancellationToken).GetAnySymbol(); - if (symbol is IMethodSymbol or IPropertySymbol or IEventSymbol or IFieldSymbol && - symbol is { ContainingType.OriginalDefinition: { } containingType } && - namedType.Equals(containingType)) + // Don't have to update if the name is already the RHS of some member access expr. + if (memberAccessExpression.Expression == nameSyntax) { - // reference to a member field an unqualified fashion. Have to qualify this. - return CreateDottedName(nameSyntax, currentNameSyntax, containingType); + // Member access expressions occur in things like the default initializer, or attribute + // arguments of the parameter. + return TryQualify(nameSyntax, currentNameSyntax); } } + else + { + // Standalone name. Try to qualify depending on if this is a type or member context. + return TryQualify(nameSyntax, currentNameSyntax); + } return currentNameSyntax; }); + SyntaxNode TryQualify( + SimpleNameSyntax originalName, + SimpleNameSyntax currentName) + { + var symbol = semanticModel.GetSymbolInfo(originalName, cancellationToken).GetAnySymbol(); + return symbol switch + { + INamedTypeSymbol { ContainingType: { } containingType } => CreateDottedName(originalName, currentName, containingType), + IMethodSymbol or IPropertySymbol or IEventSymbol or IFieldSymbol => + symbol is { ContainingType.OriginalDefinition: { } containingType } && + namedType.Equals(containingType) ? CreateDottedName(originalName, currentName, containingType) : currentName, + _ => currentName, + }; + } + SyntaxNode CreateDottedName( SimpleNameSyntax originalName, SimpleNameSyntax currentName, @@ -446,7 +458,7 @@ SyntaxNode UpdateDeclaration(SyntaxNode declaration, AssignmentExpressionSyntax // Use existing semicolon if we have it. Otherwise create a fresh one and place existing // trailing trivia after it. expressionStatement?.SemicolonToken - ?? Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(propertyDeclaration.GetTrailingTrivia())); + ?? SemicolonToken.WithTrailingTrivia(propertyDeclaration.GetTrailingTrivia())); } else { diff --git a/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs index 9896daadd5990..282bb5ea8acab 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs @@ -27,6 +27,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseSimpleUsingStatement; +using static CSharpSyntaxTokens; using static SyntaxFactory; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseSimpleUsingStatement), Shared] @@ -103,7 +104,7 @@ private static ImmutableArray Expand(UsingStatementSyntax using for (int i = 0, n = result.Count; i < n; i++) result[i] = result[i].WithAdditionalAnnotations(Formatter.Annotation); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static SyntaxTriviaList Expand(ArrayBuilder result, UsingStatementSyntax usingStatement) @@ -177,6 +178,6 @@ private static LocalDeclarationStatementSyntax Convert(UsingStatementSyntax usin usingStatement.UsingKeyword.WithAppendedTrailingTrivia(ElasticMarker), modifiers: default, usingStatement.Declaration, - Token(SyntaxKind.SemicolonToken)).WithTrailingTrivia(usingStatement.CloseParenToken.TrailingTrivia); + SemicolonToken).WithTrailingTrivia(usingStatement.CloseParenToken.TrailingTrivia); } } diff --git a/src/Analyzers/CSharp/CodeFixes/UseUtf8StringLiteral/UseUtf8StringLiteralCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseUtf8StringLiteral/UseUtf8StringLiteralCodeFixProvider.cs index 4ad6082755661..2d77751619c70 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseUtf8StringLiteral/UseUtf8StringLiteralCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseUtf8StringLiteral/UseUtf8StringLiteralCodeFixProvider.cs @@ -21,6 +21,8 @@ namespace Microsoft.CodeAnalysis.CSharp.UseUtf8StringLiteral; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseUtf8StringLiteral), Shared] internal sealed class UseUtf8StringLiteralCodeFixProvider : SyntaxEditorBasedCodeFixProvider { @@ -173,14 +175,14 @@ private static SyntaxNode CreateArgumentListWithUtf8String(BaseArgumentListSynta // We don't need to worry about leading trivia here, because anything before the current // argument will have been trailing trivia on the previous comma. var stringLiteral = CreateUtf8String(SyntaxTriviaList.Empty, stringValue, argumentList.Arguments.Last().GetTrailingTrivia(), isConvertedToReadOnlySpan); - arguments.Add(SyntaxFactory.Argument(stringLiteral)); + arguments.Add(Argument(stringLiteral)); break; } arguments.Add(argument); } - return argumentList.WithArguments(SyntaxFactory.SeparatedList(arguments)); + return argumentList.WithArguments(SeparatedList(arguments)); } private static ExpressionSyntax CreateUtf8String(SyntaxNode nodeToTakeTriviaFrom, string stringValue, bool isConvertedToReadOnlySpan) @@ -190,8 +192,8 @@ private static ExpressionSyntax CreateUtf8String(SyntaxNode nodeToTakeTriviaFrom private static ExpressionSyntax CreateUtf8String(SyntaxTriviaList leadingTrivia, string stringValue, SyntaxTriviaList trailingTrivia, bool isConvertedToReadOnlySpan) { - var stringLiteral = SyntaxFactory.LiteralExpression(SyntaxKind.Utf8StringLiteralExpression, - SyntaxFactory.Token( + var stringLiteral = LiteralExpression(SyntaxKind.Utf8StringLiteralExpression, + Token( leading: leadingTrivia, kind: SyntaxKind.Utf8StringLiteralToken, text: QuoteCharacter + stringValue + QuoteCharacter + Suffix, @@ -205,11 +207,11 @@ private static ExpressionSyntax CreateUtf8String(SyntaxTriviaList leadingTrivia, // We're replacing a byte array with a ReadOnlySpan, so if that byte array wasn't originally being // converted to the same, then we need to call .ToArray() to get things back to a byte array. - return SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression( + return InvocationExpression( + MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, stringLiteral, - SyntaxFactory.IdentifierName(nameof(ReadOnlySpan.ToArray)))) + IdentifierName(nameof(ReadOnlySpan.ToArray)))) .WithTrailingTrivia(trailingTrivia); } } diff --git a/src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs b/src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs new file mode 100644 index 0000000000000..616ccf33ffd80 --- /dev/null +++ b/src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AbstractBuiltInCodeStyleDiagnosticAnalyzer; + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Utilities; +using Xunit; + +public sealed class AbstractBuiltInCodeStyleDiagnosticAnalyzerTests +{ + [Fact] + public void VerifyDiagnosticDescriptorOrderingMaintained() + { + var ids = Enumerable.Range(10, 20).Select(item => "IDE_" + item); + + var analyzer = new TestAnalyzer(ids); + + Assert.Equal(analyzer.SupportedDiagnostics.Select(static diagnostic => diagnostic.Id), ids); + } + + private sealed class TestAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + { + public TestAnalyzer(IEnumerable ids) + : base(CreateSupportedDiagnosticsWithOptionsFromIds(ids)) + { + } + + private static ImmutableArray<(DiagnosticDescriptor, ImmutableHashSet)> CreateSupportedDiagnosticsWithOptionsFromIds(IEnumerable ids) + { + var builder = ImmutableArray.CreateBuilder<(DiagnosticDescriptor, ImmutableHashSet)>(); + foreach (var id in ids) + { + var descriptor = CreateDescriptorWithId( + id: id, + enforceOnBuild: EnforceOnBuild.Never, + hasAnyCodeStyleOption: false, + title: string.Empty); + + builder.Add((descriptor, [])); + } + + return builder.ToImmutableAndClear(); + } + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() + => throw new System.NotImplementedException(); + + protected override void InitializeWorker(AnalysisContext context) + { + } + } +} diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems index ddd1745e0f5b3..472b6d7133ff3 100644 --- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems +++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems @@ -9,6 +9,7 @@ Microsoft.CodeAnalysis.CSharp.Analyzers.UnitTests + @@ -35,6 +36,7 @@ + @@ -180,4 +182,7 @@ + + + \ No newline at end of file diff --git a/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs new file mode 100644 index 0000000000000..d3e2d6a664fa5 --- /dev/null +++ b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs @@ -0,0 +1,454 @@ +// Licensed to the .NET Foundation under one or more 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.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.MakeAnonymousFunctionStatic; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.MakeAnonymousFunctionStatic; + +using VerifyCS = CSharpCodeFixVerifier; + +[Trait(Traits.Feature, Traits.Features.CodeActionsMakeAnonymousFunctionStatic)] +public class MakeAnonymousFunctionStaticTests +{ + private static async Task TestWithCSharp9Async(string code, string fixedCode) + { + await new VerifyCS.Test + { + TestCode = code, + FixedCode = fixedCode, + LanguageVersion = LanguageVersion.CSharp9 + }.RunAsync(); + } + + [Theory] + [InlineData("i => { }")] + [InlineData("(i) => { }")] + [InlineData("delegate (int i) { }")] + public async Task TestBelowCSharp9(string anonymousFunctionSyntax) + { + var code = $$""" + using System; + + class C + { + void M() + { + N({{anonymousFunctionSyntax}}); + } + + void N(Action a) + { + } + } + """; + + await new VerifyCS.Test + { + TestCode = code, + FixedCode = code, + LanguageVersion = LanguageVersion.CSharp8 + }.RunAsync(); + } + + [Theory] + [InlineData("i => { }")] + [InlineData("(i) => { }")] + [InlineData("delegate (int i) { }")] + public async Task TestWithOptionOff(string anonymousFunctionSyntax) + { + var code = $$""" + using System; + + class C + { + void M() + { + N({{anonymousFunctionSyntax}}); + } + + void N(Action a) + { + } + } + """; + + await new VerifyCS.Test + { + TestCode = code, + FixedCode = code, + LanguageVersion = LanguageVersion.CSharp9, + Options = + { + { CSharpCodeStyleOptions.PreferStaticAnonymousFunction, false } + } + }.RunAsync(); + } + + [Theory] + [InlineData("i => { }")] + [InlineData("(i) => { }")] + [InlineData("delegate (int i) { }")] + public async Task TestMissingWhenAlreadyStatic(string anonymousFunctionSyntax) + { + var code = $$""" + using System; + + class C + { + void M() + { + N(static {{anonymousFunctionSyntax}}); + } + + void N(Action a) + { + } + } + """; + + await TestWithCSharp9Async(code, code); + } + + [Theory] + [InlineData("i => { }")] + [InlineData("(i) => { }")] + [InlineData("delegate (int i) { }")] + public async Task TestNoCaptures(string anonymousFunctionSyntax) + { + var code = $$""" + using System; + + class C + { + void M() + { + N([|{{anonymousFunctionSyntax}}|]); + } + + void N(Action a) + { + } + } + """; + + var fixedCode = $$""" + using System; + + class C + { + void M() + { + N(static {{anonymousFunctionSyntax}}); + } + + void N(Action a) + { + } + } + """; + + await TestWithCSharp9Async(code, fixedCode); + } + + [Theory] + [InlineData("i => _field")] + [InlineData("(i) => _field")] + [InlineData("delegate (int i) { return _field; }")] + public async Task TestCapturesThis(string anonymousFunctionSyntax) + { + var code = $$""" + using System; + + class C + { + private int _field; + + void M() + { + N({{anonymousFunctionSyntax}}); + } + + void N(Func f) + { + } + } + """; + + await TestWithCSharp9Async(code, code); + } + + [Theory] + [InlineData("i => GetValueFromStaticMethod()")] + [InlineData("(i) => GetValueFromStaticMethod()")] + [InlineData("delegate (int i) { return GetValueFromStaticMethod(); }")] + public async Task TestNoCaptures_ReferencesStaticMethod(string anonymousFunctionSyntax) + { + var code = $$""" + using System; + + class C + { + private static int GetValueFromStaticMethod() => 0; + + void M() + { + N([|{{anonymousFunctionSyntax}}|]); + } + + void N(Func f) + { + } + } + """; + + var fixedCode = $$""" + using System; + + class C + { + private static int GetValueFromStaticMethod() => 0; + + void M() + { + N(static {{anonymousFunctionSyntax}}); + } + + void N(Func f) + { + } + } + """; + + await TestWithCSharp9Async(code, fixedCode); + } + + [Theory] + [InlineData("i => x")] + [InlineData("(i) => x")] + [InlineData("delegate (int i) { return x; }")] + public async Task TestCapturesParameter(string anonymousFunctionSyntax) + { + var code = $$""" + using System; + + class C + { + void M(int x) + { + N({{anonymousFunctionSyntax}}); + } + + void N(Func f) + { + } + } + """; + + await TestWithCSharp9Async(code, code); + } + + [Theory] + [InlineData("i => i")] + [InlineData("(i) => i")] + [InlineData("delegate (int i) { return i; }")] + public async Task TestNoCaptures_SameFunctionParameterNameAsOuterParameterName(string anonymousFunctionSyntax) + { + var code = $$""" + using System; + + class C + { + void M(int i) + { + N([|{{anonymousFunctionSyntax}}|]); + } + + void N(Func f) + { + } + } + """; + + var fixedCode = $$""" + using System; + + class C + { + void M(int i) + { + N(static {{anonymousFunctionSyntax}}); + } + + void N(Func f) + { + } + } + """; + + await TestWithCSharp9Async(code, fixedCode); + } + + [Theory] + [InlineData("i => x")] + [InlineData("(i) => x")] + [InlineData("delegate (int i) { return x; }")] + public async Task TestCapturesLocal(string anonymousFunctionSyntax) + { + var code = $$""" + using System; + + class C + { + void M() + { + int x = 0; + N({{anonymousFunctionSyntax}}); + } + + void N(Func f) + { + } + } + """; + + await TestWithCSharp9Async(code, code); + } + + [Theory] + [InlineData("i => i")] + [InlineData("(i) => i")] + [InlineData("delegate (int i) { return i; }")] + public async Task TestNoCaptures_SameFunctionParameterNameAsOuterLocalName(string anonymousFunctionSyntax) + { + var code = $$""" + using System; + + class C + { + void M() + { + int i = 0; + N([|{{anonymousFunctionSyntax}}|]); + } + + void N(Func f) + { + } + } + """; + + var fixedCode = $$""" + using System; + + class C + { + void M() + { + int i = 0; + N(static {{anonymousFunctionSyntax}}); + } + + void N(Func f) + { + } + } + """; + + await TestWithCSharp9Async(code, fixedCode); + } + + [Fact] + public async Task TestNestedLambdasWithNoCaptures() + { + var code = """ + using System; + + class C + { + void M() + { + N([|() => + { + Action a = [|() => { }|]; + }|]); + } + + void N(Action a) + { + } + } + """; + + var fixedCode = """ + using System; + + class C + { + void M() + { + N(static () => + { + Action a = static () => { }; + }); + } + + void N(Action a) + { + } + } + """; + + await TestWithCSharp9Async(code, fixedCode); + } + + [Fact] + public async Task TestNestedAnonymousMethodsWithNoCaptures() + { + var code = """ + using System; + + class C + { + void M() + { + N([|delegate () + { + Action a = [|delegate () { }|]; + }|]); + } + + void N(Action a) + { + } + } + """; + + var fixedCode = """ + using System; + + class C + { + void M() + { + N(static delegate () + { + Action a = static delegate () { }; + }); + } + + void N(Action a) + { + } + } + """; + + await TestWithCSharp9Async(code, fixedCode); + } +} diff --git a/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs b/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs index 3a6d90d4f4e8a..980df80b8c730 100644 --- a/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs +++ b/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; using Xunit.Abstractions; @@ -20,7 +21,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.MakeFieldReadonly [Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] public class MakeFieldReadonlyTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor { - private static readonly ParseOptions s_strictFeatureFlag = CSharpParseOptions.Default.WithFeatures([new KeyValuePair("strict", "true")]); + private static readonly ParseOptions s_strictFeatureFlag = CSharpParseOptions.Default.WithFeatures([KeyValuePairUtil.Create("strict", "true")]); public MakeFieldReadonlyTests(ITestOutputHelper logger) : base(logger) diff --git a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs index b8be68635065b..3418d1e2e4205 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs @@ -70,7 +70,6 @@ static void Main() static void Goo(bool a, bool b) { } } """, - """ class Program { @@ -102,7 +101,6 @@ static void Main() } } """, - """ using System; @@ -132,7 +130,6 @@ static void Main() } } """, - """ using System; @@ -480,7 +477,6 @@ static void M1(int? i1 = [|(int?)|]null) } } """, - """ class Program { @@ -504,7 +500,6 @@ static long M2() } } """, - """ class Program { @@ -530,7 +525,6 @@ static void M1() } } """, - """ using System; class Program @@ -557,7 +551,6 @@ static void M1() } } """, - """ using System; class Program @@ -804,7 +797,6 @@ static void Main(string[] args) public static void M1(int i) { } } """, - """ using System; class Test @@ -838,7 +830,6 @@ public static void Main() } } """, - """ using System; class Test @@ -871,7 +862,6 @@ static void Main() } } """, - """ class Test { @@ -906,7 +896,6 @@ static void Main() } } """, - """ class Test { @@ -939,7 +928,6 @@ static void Main() } } """, - """ using System.Collections.Generic; @@ -1061,7 +1049,6 @@ static void Goo() } } """, - """ using System; class MyAction @@ -1139,7 +1126,6 @@ static void Goo() } } """, - """ using System; @@ -1285,7 +1271,6 @@ static void Main() } } """, - """ using System; @@ -1463,7 +1448,6 @@ static bool Any(this ICollection s, Func predicate) } } """, - """ using System; using System.Collections.Generic; @@ -1507,7 +1491,6 @@ static void Main() } } """, - """ using System; @@ -1874,7 +1857,6 @@ static void Main() } } """, - """ class Program { @@ -1904,7 +1886,6 @@ static void Goo() } } """, - """ class X { @@ -1932,7 +1913,6 @@ static void Main() } } """, - """ static class C { @@ -1960,7 +1940,6 @@ static void Main() } } """, - """ class Program { @@ -2101,7 +2080,6 @@ class C C() : this([|(int)|]1) { } } """, - """ class C { @@ -2125,7 +2103,6 @@ class C C() : this([|(IEnumerable)|]"") { } } """, - """ using System.Collections; @@ -2177,7 +2154,6 @@ static void Main() } } """, - """ static class C { @@ -3476,7 +3452,6 @@ static void Main() } } """, - """ using System; @@ -3510,7 +3485,6 @@ static void Main() } } """, - """ using System; @@ -3541,7 +3515,6 @@ static void Main(string s) } } """, - """ using System; using System.Collections.Generic; @@ -3575,7 +3548,6 @@ static void Main() } } """, - """ using System; @@ -3878,7 +3850,6 @@ static void Main() } } """, - """ class C { @@ -3907,7 +3878,6 @@ static void Main() } } """, - """ class C { @@ -3936,7 +3906,6 @@ static void Main() } } """, - """ class C { @@ -3965,7 +3934,6 @@ static void Main() } } """, - """ class C { @@ -3994,7 +3962,6 @@ static void Main() } } """, - """ class C { @@ -4297,7 +4264,6 @@ public override void Goo(int x = 1) } } """, - """ using System; @@ -4341,7 +4307,6 @@ static void Main() static void Goo(this string x) { } } """, - """ using System; @@ -5023,7 +4988,6 @@ public enum Directions } } """, - """ class Program { @@ -10134,7 +10098,6 @@ void Main(int? n) } } """, - """ class Program { @@ -10159,7 +10122,6 @@ void Main(int? n) } } """, - """ class Program { @@ -13830,4 +13792,47 @@ public static implicit operator Goo(Action value) ReferenceAssemblies = ReferenceAssemblies.Net.Net80, }.RunAsync(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72933")] + public async Task RemoveCollectionExpressionCastToArray() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + void Goo(char[] input) + { + } + + void Goo(string input) + { + } + + void X() + { + Goo([|(char[])|]['a']); + } + } + """, + FixedCode = """ + class C + { + void Goo(char[] input) + { + } + + void Goo(string input) + { + } + + void X() + { + Goo(['a']); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } } diff --git a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs index bd5d41f38c5ce..fec3c88739190 100644 --- a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs +++ b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs @@ -5179,4 +5179,245 @@ class C LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72640")] + public async Task TestDynamic1() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + class C + { + public void Test(dynamic obj) + { + obj.arr = new byte[] { 1, 2, 3 }; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72640")] + public async Task TestDynamic2() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + class C + { + public void Test(dynamic obj) + { + obj.arr = (new byte[] { 1, 2, 3 })!; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72640")] + public async Task TestDynamic3() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + class C + { + public void Test(dynamic obj) + { + obj = new byte[] { 1, 2, 3 }; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72640")] + public async Task TestDynamic4() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + class C + { + public void Test(dynamic obj) + { + Test(new byte[] { 1, 2, 3 }); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72640")] + public async Task TestDynamic5() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + class C + { + public void Test(dynamic obj) + { + Test((new byte[] { 1, 2, 3 })!); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72640")] + public async Task TestDynamic6() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + class C + { + public void Test(dynamic obj) + { + Test1(obj, new int?[] { 3 }); + } + + private void Test1(dynamic obj, params int?[][] args) + { + } + } + """, + LanguageVersion = LanguageVersion.Preview, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72640")] + public async Task TestDynamic7() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + class C + { + public void Test(dynamic obj) + { + Test1(obj, [|[|new|] int?[]|] { 3 }); + } + + private void Test1(dynamic obj, int?[] args) + { + } + } + """, + FixedCode = + """ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + class C + { + public void Test(dynamic obj) + { + Test1(obj, [3]); + } + + private void Test1(dynamic obj, int?[] args) + { + } + } + """, + LanguageVersion = LanguageVersion.Preview, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72640")] + public async Task TestDynamic6_CSharp12() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + class C + { + public void Test(dynamic obj) + { + Test1(obj, [|[|new|] int?[]|] { 3 }); + } + + private void Test1(dynamic obj, params int?[][] args) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72640")] + public async Task TestDynamic7_CSharp12() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + class C + { + public void Test(dynamic obj) + { + Test1(obj, [|[|new|] int?[]|] { 3 }); + } + + private void Test1(dynamic obj, int?[] args) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } } diff --git a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs index bd0fbe4312df9..b4552f98d0246 100644 --- a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs +++ b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs @@ -49,19 +49,19 @@ public void Clear() { } } """; - public static readonly IEnumerable FailureCreationPatterns = new[] - { - new[] {"var builder = ImmutableArray.CreateBuilder();" }, - new[] {"var builder = ArrayBuilder.GetInstance();" }, - new[] {"using var _ = ArrayBuilder.GetInstance(out var builder);" }, - }; - - public static readonly IEnumerable SuccessCreationPatterns = new[] - { - new[] {"[|var builder = ImmutableArray.[|CreateBuilder|]();|]" }, - new[] {"[|var builder = ArrayBuilder.[|GetInstance|]();|]" }, - new[] {"[|using var _ = ArrayBuilder.[|GetInstance|](out var builder);|]" }, - }; + public static readonly IEnumerable FailureCreationPatterns = + [ + ["var builder = ImmutableArray.CreateBuilder();"], + ["var builder = ArrayBuilder.GetInstance();"], + ["using var _ = ArrayBuilder.GetInstance(out var builder);"], + ]; + + public static readonly IEnumerable SuccessCreationPatterns = + [ + ["[|var builder = ImmutableArray.[|CreateBuilder|]();|]"], + ["[|var builder = ArrayBuilder.[|GetInstance|]();|]"], + ["[|using var _ = ArrayBuilder.[|GetInstance|](out var builder);|]"], + ]; [Theory, MemberData(nameof(FailureCreationPatterns))] public async Task TestNotInCSharp11(string pattern) diff --git a/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs b/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs index 37e3bdc1f4dac..76762105c8fce 100644 --- a/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs +++ b/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs @@ -5270,16 +5270,6 @@ public void Add(string s) { } { OutputKind = OutputKind.DynamicallyLinkedLibrary, }, - FixedState = - { - ExpectedDiagnostics = - { - // /0/Test0.cs(6,26): error CS1503: Argument 1: cannot convert from 'object' to 'string' - DiagnosticResult.CompilerError("CS1503").WithSpan(6, 26, 6, 36).WithArguments("1", "object", "string"), - // /0/Test0.cs(6,26): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'MyCollection.Add(string)'. - DiagnosticResult.CompilerError("CS9215").WithSpan(6, 26, 6, 36).WithArguments("object", "MyCollection.Add(string)"), - } - } }.RunAsync(); } @@ -5356,4 +5346,324 @@ void M(int[] x) ReferenceAssemblies = ReferenceAssemblies.Net.Net80, }.RunAsync(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72169")] + public async Task TestInValueTuple1() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, List) M() { + return (42, [|new|] List()); + } + } + """, + FixedCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, List) M() { + return (42, []); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72169")] + public async Task TestInValueTuple2() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, List) M() { + return (42, [|new|] List() { }); + } + } + """, + FixedCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, List) M() { + return (42, []); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72169")] + public async Task TestInValueTuple3() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, List) M() { + return (42, [|new|] List { 1, 2, 3 }); + } + } + """, + FixedCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, List) M() { + return (42, [1, 2, 3]); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72169")] + public async Task TestInValueTuple4() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, List) M() { + return (42, [|new|] List + { + 1, + 2, + 3 + }); + } + } + """, + FixedCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, List) M() { + return (42, + [ + 1, + 2, + 3 + ]); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72169")] + public async Task TestInValueTuple5() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, Func>) M() { + return (42, () => [|new|] List()); + } + } + """, + FixedCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, Func>) M() { + return (42, () => []); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72169")] + public async Task TestInValueTuple6() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, Func>) M() { + return (42, () => [|new|] List() { 1, 2, 3 }); + } + } + """, + FixedCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, Func>) M() { + return (42, () => [1, 2, 3]); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72169")] + public async Task TestInValueTuple7() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, Func>) M() { + return (42, () => [|new|] List + { + 1, + 2, + 3 + }); + } + } + """, + FixedCode = + """ + using System; + using System.Collections.Generic; + + class C + { + public (int, Func>) M() { + return (42, () => + [ + 1, + 2, + 3 + ]); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72701")] + public async Task TestNotWithObservableCollection1() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + + class C + { + void M() + { + IList strings = new ObservableCollection(); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72701")] + public async Task TestNotWithObservableCollection2() + { + await new VerifyCS.Test + { + TestCode = + """ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + + class C + { + void M() + { + ObservableCollection strings = [|new|] ObservableCollection(); + } + } + """, + FixedCode = + """ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + + class C + { + void M() + { + ObservableCollection strings = []; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } } diff --git a/src/Analyzers/CSharp/Tests/UsePrimaryConstructor/UsePrimaryConstructorTests.cs b/src/Analyzers/CSharp/Tests/UsePrimaryConstructor/UsePrimaryConstructorTests.cs index ad2fe39cb083e..7e695ec9d2089 100644 --- a/src/Analyzers/CSharp/Tests/UsePrimaryConstructor/UsePrimaryConstructorTests.cs +++ b/src/Analyzers/CSharp/Tests/UsePrimaryConstructor/UsePrimaryConstructorTests.cs @@ -4028,4 +4028,47 @@ public C(Type type) ReferenceAssemblies = ReferenceAssemblies.Net.Net80, }.RunAsync(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72784")] + public async Task TestQualifyNestedEnum() + { + await new VerifyCS.Test + { + TestCode = """ + public class MyClass + { + public [|MyClass|](EnumInClass.MyEnum myEnum = EnumInClass.MyEnum.Default) + { + this.MyEnum = myEnum; + } + + public EnumInClass.MyEnum MyEnum { get; set; } + } + + public class EnumInClass + { + public enum MyEnum + { + Default + } + } + """, + FixedCode = """ + public class MyClass(EnumInClass.MyEnum myEnum = EnumInClass.MyEnum.Default) + { + public EnumInClass.MyEnum MyEnum { get; set; } = myEnum; + } + + public class EnumInClass + { + public enum MyEnum + { + Default + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } } diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs index 2a9338532b838..ded082441f754 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; @@ -81,12 +80,12 @@ protected AbstractBuiltInCodeStyleDiagnosticAnalyzer( /// /// Constructor for a code style analyzer with a multiple diagnostic descriptors with a code style editorconfig option that can be used to configure each descriptor. /// - protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableDictionary supportedDiagnosticsWithOptions) - : this(supportedDiagnosticsWithOptions.Keys.ToImmutableArray()) + protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, IOption2 Option)> supportedDiagnosticsWithOptions) + : this(supportedDiagnosticsWithOptions.SelectAsArray(static item => item.Descriptor)) { foreach (var (descriptor, option) in supportedDiagnosticsWithOptions) { - Debug.Assert(option != null == descriptor.CustomTags.Contains(WellKnownDiagnosticTags.CustomSeverityConfigurable)); + Debug.Assert(option is { Definition.DefaultValue: ICodeStyleOption } == descriptor.CustomTags.Contains(WellKnownDiagnosticTags.CustomSeverityConfigurable)); AddDiagnosticIdToOptionMapping(descriptor.Id, option); } } @@ -94,8 +93,8 @@ protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableDictionary /// Constructor for a code style analyzer with multiple diagnostic descriptors with zero or more code style editorconfig options that can be used to configure each descriptor. /// - protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableDictionary> supportedDiagnosticsWithOptions) - : this(supportedDiagnosticsWithOptions.Keys.ToImmutableArray()) + protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, ImmutableHashSet Options)> supportedDiagnosticsWithOptions) + : this(supportedDiagnosticsWithOptions.SelectAsArray(static item => item.Descriptor)) { foreach (var (descriptor, options) in supportedDiagnosticsWithOptions) { diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs index 59d80030b5808..b91fd230bf02f 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeStyle; diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs index 4ca8ddd49631f..922dc5ad9ff11 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs @@ -100,19 +100,19 @@ protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableArray /// Constructor for a code style analyzer with a multiple diagnostic descriptors with a code style options that can be used to configure each descriptor. /// - protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableDictionary supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) + protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, IOption2 Option)> supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) : base(supportedDiagnosticsWithOptions) { - AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Keys, fadingOption); + AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Select(static item => item.Descriptor), fadingOption); } /// /// Constructor for a code style analyzer with multiple diagnostic descriptors with zero or more code style options that can be used to configure each descriptor. /// - protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableDictionary> supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) + protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, ImmutableHashSet Options)> supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) : base(supportedDiagnosticsWithOptions) { - AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Keys, fadingOption); + AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Select(static item => item.Descriptor), fadingOption); } private static void AddDiagnosticIdToFadingOptionMapping(string diagnosticId, PerLanguageOption2? fadingOption) diff --git a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs index 65cd9ad9ee9e1..99b146a48c43e 100644 --- a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs +++ b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs @@ -96,6 +96,7 @@ internal static class EnforceOnBuildValues public const EnforceOnBuild UseCollectionExpressionForCreate = /*IDE0303*/ EnforceOnBuild.Recommended; public const EnforceOnBuild UseCollectionExpressionForBuilder = /*IDE0304*/ EnforceOnBuild.Recommended; public const EnforceOnBuild UseCollectionExpressionForFluent = /*IDE0305*/ EnforceOnBuild.Recommended; + public const EnforceOnBuild MakeAnonymousFunctionStatic = /*IDE0320*/ 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/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs index e356bc1c9b206..f25e461358b7d 100644 --- a/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using System.IO; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.FileHeaders; @@ -21,12 +19,14 @@ internal abstract class AbstractFileHeaderDiagnosticAnalyzer : AbstractBuiltInCo private static readonly DiagnosticDescriptor s_missingHeaderDescriptor = CreateDescriptorForFileHeader(s_missingHeaderTitle, s_missingHeaderMessage); private static DiagnosticDescriptor CreateDescriptorForFileHeader(LocalizableString title, LocalizableString message) - => CreateDescriptorWithId(IDEDiagnosticIds.FileHeaderMismatch, EnforceOnBuildValues.FileHeaderMismatch, hasAnyCodeStyleOption: true, title, message); + => CreateDescriptorWithId(IDEDiagnosticIds.FileHeaderMismatch, EnforceOnBuildValues.FileHeaderMismatch, hasAnyCodeStyleOption: false, title, message); protected AbstractFileHeaderDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_invalidHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate) - .Add(s_missingHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate)) + : base( + [ + (s_invalidHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate), + (s_missingHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate) + ]) { } diff --git a/src/Analyzers/Core/Analyzers/Formatting/FormatterHelper.cs b/src/Analyzers/Core/Analyzers/Formatting/FormatterHelper.cs index 7b4a7640b7dde..cc91eb9d9ddc7 100644 --- a/src/Analyzers/Core/Analyzers/Formatting/FormatterHelper.cs +++ b/src/Analyzers/Core/Analyzers/Formatting/FormatterHelper.cs @@ -30,10 +30,10 @@ internal static IEnumerable GetDefaultFormattingRules(IS /// An optional cancellation token. /// The formatted tree's root node. public static SyntaxNode Format(SyntaxNode node, ISyntaxFormatting syntaxFormattingService, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => Format(node, SpecializedCollections.SingletonEnumerable(node.FullSpan), syntaxFormattingService, options, rules: null, cancellationToken: cancellationToken); + => Format(node, [node.FullSpan], syntaxFormattingService, options, rules: null, cancellationToken: cancellationToken); public static SyntaxNode Format(SyntaxNode node, TextSpan spanToFormat, ISyntaxFormatting syntaxFormattingService, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => Format(node, SpecializedCollections.SingletonEnumerable(spanToFormat), syntaxFormattingService, options, rules: null, cancellationToken: cancellationToken); + => Format(node, [spanToFormat], syntaxFormattingService, options, rules: null, cancellationToken: cancellationToken); /// /// Formats the whitespace of a syntax tree. @@ -63,5 +63,5 @@ internal static IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerab /// An optional cancellation token. /// The changes necessary to format the tree. public static IList GetFormattedTextChanges(SyntaxNode node, ISyntaxFormatting syntaxFormattingService, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => GetFormattedTextChanges(node, SpecializedCollections.SingletonEnumerable(node.FullSpan), syntaxFormattingService, options, rules: null, cancellationToken: cancellationToken); + => GetFormattedTextChanges(node, [node.FullSpan], syntaxFormattingService, options, rules: null, cancellationToken: cancellationToken); } diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs index 7c3a5d350984d..b657d4cd0f720 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs @@ -69,7 +69,7 @@ public static void AddOptionMapping(string diagnosticId, ImmutableHashSet new ConcurrentDictionary>()); - AddOptionMapping(map, diagnosticId, languageGroup.ToImmutableHashSet()); + AddOptionMapping(map, diagnosticId, [.. languageGroup]); } } } diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs index fefb859cdeb09..7d4b190395472 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs @@ -199,6 +199,8 @@ internal static class IDEDiagnosticIds public const string UseCollectionExpressionForBuilderDiagnosticId = "IDE0304"; public const string UseCollectionExpressionForFluentDiagnosticId = "IDE0305"; + public const string MakeAnonymousFunctionStaticDiagnosticId = "IDE0320"; + // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; public const string AnalyzerDependencyConflictId = "IDE1002"; diff --git a/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchExpressionHelpers.cs b/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchExpressionHelpers.cs index 0404194fec0ce..2e4336069220e 100644 --- a/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchExpressionHelpers.cs +++ b/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchExpressionHelpers.cs @@ -35,7 +35,7 @@ public static ICollection GetMissingEnumMembers(ISwitchExpressionOperat } } - return SpecializedCollections.EmptyCollection(); + return []; } public static bool HasNullSwitchArm(ISwitchExpressionOperation operation) diff --git a/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchStatementHelpers.cs b/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchStatementHelpers.cs index 501ae57efc9f3..710252b1e2b38 100644 --- a/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchStatementHelpers.cs +++ b/src/Analyzers/Core/Analyzers/PopulateSwitch/PopulateSwitchStatementHelpers.cs @@ -71,7 +71,7 @@ public static ICollection GetMissingEnumMembers(ISwitchOperation switch if (!TryGetAllEnumMembers(switchExpressionType, enumMembers) || !TryRemoveExistingEnumMembers(switchStatement, enumMembers)) { - return SpecializedCollections.EmptyCollection(); + return []; } } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs index 690ac016a84b8..6c860d117931f 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs @@ -28,7 +28,7 @@ internal abstract class AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer 0) + while (stack.TryPop(out var currentType)) { - var currentType = stack.Pop(); - // Add the doc comments on the type itself. AddDocumentationComments(currentType, documentationComments); @@ -663,8 +661,7 @@ private void AddDebuggerDisplayAttributeArguments(INamedTypeSymbol namedTypeSymb AddDebuggerDisplayAttributeArguments(nestedType, builder); break; - case IPropertySymbol _: - case IFieldSymbol _: + case IPropertySymbol or IFieldSymbol: AddDebuggerDisplayAttributeArgumentsCore(member, builder); break; } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs index 0240858e09729..42846f4bc11e2 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs @@ -96,11 +96,13 @@ internal abstract partial class AbstractRemoveUnusedParametersAndValuesDiagnosti protected AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer( Option2> unusedValueExpressionStatementOption, Option2> unusedValueAssignmentOption) - : base(ImmutableDictionary.Empty - .Add(s_expressionValueIsUnusedRule, unusedValueExpressionStatementOption) - .Add(s_valueAssignedIsUnusedRule, unusedValueAssignmentOption) - .Add(s_unusedParameterRule, CodeStyleOptions2.UnusedParameters), - fadingOption: null) + : base( + [ + (s_expressionValueIsUnusedRule, unusedValueExpressionStatementOption), + (s_valueAssignedIsUnusedRule, unusedValueAssignmentOption), + (s_unusedParameterRule, CodeStyleOptions2.UnusedParameters) + ], + fadingOption: null) { } diff --git a/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs b/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs index a37aa66a8d07f..fdaf88e7069f3 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs @@ -4,19 +4,16 @@ // #define LOG -using System; using System.Collections.Concurrent; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -64,15 +61,17 @@ internal abstract class SimplifyTypeNamesDiagnosticAnalyzerBase>.Empty - .Add(s_descriptorSimplifyNames, []) - .Add(s_descriptorSimplifyMemberAccess, []) - .Add(s_descriptorPreferBuiltinOrFrameworkType, - [ - CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, - CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, - ]), - fadingOption: null) + : base( + [ + (s_descriptorSimplifyNames, []), + (s_descriptorSimplifyMemberAccess, []), + (s_descriptorPreferBuiltinOrFrameworkType, + [ + CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, + CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, + ]) + ], + fadingOption: null) { } diff --git a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractObjectCreationExpressionAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractObjectCreationExpressionAnalyzer.cs index ba361e6411ed3..e1cb4dc271488 100644 --- a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractObjectCreationExpressionAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractObjectCreationExpressionAnalyzer.cs @@ -84,7 +84,7 @@ protected ImmutableArray AnalyzeWorker(CancellationToken cancellationTok if (!TryAddMatches(matches, cancellationToken)) return default; - return matches.ToImmutable(); + return matches.ToImmutableAndClear(); } protected UpdateExpressionState? TryInitializeState( diff --git a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs index ea645fb31ee2a..a663612d196d2 100644 --- a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs @@ -8,7 +8,6 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.CodeStyle; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -82,9 +81,10 @@ public override DiagnosticAnalyzerCategory GetAnalyzerCategory() isUnnecessary: true); protected AbstractUseCollectionInitializerDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_descriptor, CodeStyleOptions2.PreferCollectionInitializer) - .Add(s_unnecessaryCodeDescriptor, CodeStyleOptions2.PreferCollectionInitializer)) + : base( + [ + (s_descriptor, CodeStyleOptions2.PreferCollectionInitializer) + ]) { } diff --git a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/UpdateExpressionState.cs b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/UpdateExpressionState.cs index deb58119c61fc..d93969a9f41db 100644 --- a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/UpdateExpressionState.cs +++ b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/UpdateExpressionState.cs @@ -71,7 +71,7 @@ public UpdateExpressionState( public IEnumerable GetSubsequentStatements() => ContainingStatement is null - ? SpecializedCollections.EmptyEnumerable() + ? [] : UseCollectionInitializerHelpers.GetSubsequentStatements(SyntaxFacts, ContainingStatement); /// diff --git a/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs index 8e1c39d240270..aabbb18b1eb49 100644 --- a/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; @@ -62,9 +61,10 @@ internal abstract partial class AbstractUseObjectInitializerDiagnosticAnalyzer< protected abstract TAnalyzer GetAnalyzer(); protected AbstractUseObjectInitializerDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_descriptor, CodeStyleOptions2.PreferObjectInitializer) - .Add(s_unnecessaryCodeDescriptor, CodeStyleOptions2.PreferObjectInitializer)) + : base( + [ + (s_descriptor, CodeStyleOptions2.PreferObjectInitializer) + ]) { } diff --git a/src/Analyzers/Core/CodeFixes/AddExplicitCast/Fixer.cs b/src/Analyzers/Core/CodeFixes/AddExplicitCast/Fixer.cs index a75028498ced1..11c73c424eac5 100644 --- a/src/Analyzers/Core/CodeFixes/AddExplicitCast/Fixer.cs +++ b/src/Analyzers/Core/CodeFixes/AddExplicitCast/Fixer.cs @@ -73,7 +73,7 @@ protected abstract class Fixer(semanticModel)); - return mutablePotentialConversionTypes.ToImmutable(); + return mutablePotentialConversionTypes.ToImmutableAndClear(); } /// diff --git a/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs index 117b30f2ccd8d..03be933e28046 100644 --- a/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -244,7 +245,7 @@ private void RegisterFixForMethodOverloads( ImmutableArray NestByOverload() { - using var _ = ArrayBuilder.GetInstance(codeFixData.Length, out var builder); + var builder = new FixedSizeArrayBuilder(codeFixData.Length); foreach (var data in codeFixData) { // We create the mandatory data.CreateChangedSolutionNonCascading fix first. @@ -276,12 +277,12 @@ ImmutableArray NestByOverload() builder.Add(codeAction); } - return builder.ToImmutableAndClear(); + return builder.MoveToImmutable(); } ImmutableArray NestByCascading() { - using var _ = ArrayBuilder.GetInstance(capacity: 2, out var builder); + using var builder = TemporaryArray.Empty; var nonCascadingActions = codeFixData.SelectAsArray(data => { @@ -312,7 +313,7 @@ ImmutableArray NestByCascading() builder.Add(CodeAction.Create(nestedCascadingTitle, cascadingActions, isInlinable: false)); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } @@ -321,7 +322,7 @@ private ImmutableArray PrepareCreationOfCodeActions( SeparatedSyntaxList arguments, ImmutableArray> methodsAndArgumentsToAdd) { - using var _ = ArrayBuilder.GetInstance(methodsAndArgumentsToAdd.Length, out var builder); + var builder = new FixedSizeArrayBuilder(methodsAndArgumentsToAdd.Length); // Order by the furthest argument index to the nearest argument index. The ones with // larger argument indexes mean that we matched more earlier arguments (and thus are @@ -343,7 +344,7 @@ private ImmutableArray PrepareCreationOfCodeActions( builder.Add(codeFixData); } - return builder.ToImmutable(); + return builder.MoveToImmutable(); } private static string GetCodeFixTitle(string resourceString, IMethodSymbol methodToUpdate, bool includeParameters) diff --git a/src/Analyzers/Core/CodeFixes/MatchFolderAndNamespace/AbstractChangeNamespaceToMatchFolderCodeFixProvider.CustomFixAllProvider.cs b/src/Analyzers/Core/CodeFixes/MatchFolderAndNamespace/AbstractChangeNamespaceToMatchFolderCodeFixProvider.CustomFixAllProvider.cs index b8455ab05fb41..3a25c37639087 100644 --- a/src/Analyzers/Core/CodeFixes/MatchFolderAndNamespace/AbstractChangeNamespaceToMatchFolderCodeFixProvider.CustomFixAllProvider.cs +++ b/src/Analyzers/Core/CodeFixes/MatchFolderAndNamespace/AbstractChangeNamespaceToMatchFolderCodeFixProvider.CustomFixAllProvider.cs @@ -63,7 +63,7 @@ static async Task> GetSolutionDiagnosticsAsync(FixAll diagnostics.AddRange(projectDiagnostics); } - return diagnostics.ToImmutable(); + return diagnostics.ToImmutableAndClear(); } } diff --git a/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs index 87f345e571b6b..bdf383bd56b35 100644 --- a/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs @@ -148,10 +148,7 @@ public FixNameCodeAction( } protected override async Task> ComputePreviewOperationsAsync(CancellationToken cancellationToken) - { - return SpecializedCollections.SingletonEnumerable( - new ApplyChangesOperation(await _createChangedSolutionAsync(cancellationToken).ConfigureAwait(false))); - } + => [new ApplyChangesOperation(await _createChangedSolutionAsync(cancellationToken).ConfigureAwait(false))]; protected override async Task> ComputeOperationsAsync(IProgress progress, CancellationToken cancellationToken) { diff --git a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs index b9b3280c25209..bb030e6eb15c5 100644 --- a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs +++ b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs @@ -73,6 +73,7 @@ internal static class PredefinedCodeFixProviderNames public const string JsonDetection = nameof(JsonDetection); public const string MakeFieldReadonly = nameof(MakeFieldReadonly); public const string MakeLocalFunctionStatic = nameof(MakeLocalFunctionStatic); + public const string MakeAnonymousFunctionStatic = nameof(MakeAnonymousFunctionStatic); public const string MakeMemberRequired = nameof(MakeMemberRequired); public const string MakeMemberStatic = nameof(MakeMemberStatic); public const string MakeMethodSynchronous = nameof(MakeMethodSynchronous); diff --git a/src/CodeStyle/Core/Tests/UnitTestUtilities/Microsoft.CodeAnalysis.CodeStyle.UnitTestUtilities.csproj b/src/CodeStyle/Core/Tests/UnitTestUtilities/Microsoft.CodeAnalysis.CodeStyle.UnitTestUtilities.csproj index 1bcec3fcd1e39..69807d0f912fa 100644 --- a/src/CodeStyle/Core/Tests/UnitTestUtilities/Microsoft.CodeAnalysis.CodeStyle.UnitTestUtilities.csproj +++ b/src/CodeStyle/Core/Tests/UnitTestUtilities/Microsoft.CodeAnalysis.CodeStyle.UnitTestUtilities.csproj @@ -24,6 +24,7 @@ + diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 918887857b42b..6483d9690dd5c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -310,8 +310,19 @@ private static bool RequiresRefOrOut(BindValueKind kind) #nullable enable - private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundIndexerAccess indexerAccess, BindValueKind valueKind, BindingDiagnosticBag diagnostics) + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// This flag and the assertion below help catch any new assignment scenarios and + /// make them aware of this subtlety. + /// The flag itself doesn't affect semantic analysis beyond the assertion. + /// + private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundIndexerAccess indexerAccess, BindValueKind valueKind, BindingDiagnosticBag diagnostics, bool dynamificationOfAssignmentResultIsHandled = false) { + Debug.Assert((valueKind & BindValueKind.Assignable) == 0 || (valueKind & BindValueKind.RefersToLocation) != 0 || dynamificationOfAssignmentResultIsHandled); + var useSetAccessor = valueKind == BindValueKind.Assignable && !indexerAccess.Indexer.ReturnsByRef; var accessorForDefaultArguments = useSetAccessor ? indexerAccess.Indexer.GetOwnOrInheritedSetMethod() @@ -368,7 +379,7 @@ private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundI } } - BindDefaultArgumentsAndParamsCollection(indexerAccess.Syntax, parameters, argumentsBuilder, refKindsBuilderOpt, namesBuilder, ref argsToParams, out defaultArguments, indexerAccess.Expanded, enableCallerInfo: true, diagnostics); + BindDefaultArguments(indexerAccess.Syntax, parameters, argumentsBuilder, refKindsBuilderOpt, namesBuilder, ref argsToParams, out defaultArguments, indexerAccess.Expanded, enableCallerInfo: true, diagnostics); if (namesBuilder is object) { @@ -404,15 +415,26 @@ private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundI /// method returns a BoundBadExpression node. The method returns the original /// expression without generating any error if the expression has errors. /// - private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, BindingDiagnosticBag diagnostics) + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// This flag and the assertion below help catch any new assignment scenarios and + /// make them aware of this subtlety. + /// The flag itself doesn't affect semantic analysis beyond the assertion. + /// + private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, BindingDiagnosticBag diagnostics, bool dynamificationOfAssignmentResultIsHandled = false) { + Debug.Assert((valueKind & BindValueKind.Assignable) == 0 || (valueKind & BindValueKind.RefersToLocation) != 0 || dynamificationOfAssignmentResultIsHandled); + switch (expr.Kind) { case BoundKind.PropertyGroup: expr = BindIndexedPropertyAccess((BoundPropertyGroup)expr, mustHaveAllOptionalParameters: false, diagnostics: diagnostics); if (expr is BoundIndexerAccess indexerAccess) { - expr = BindIndexerDefaultArgumentsAndParamsCollection(indexerAccess, valueKind, diagnostics); + expr = BindIndexerDefaultArgumentsAndParamsCollection(indexerAccess, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: dynamificationOfAssignmentResultIsHandled); } break; @@ -430,7 +452,7 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind return expr; case BoundKind.IndexerAccess: - expr = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)expr, valueKind, diagnostics); + expr = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)expr, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: dynamificationOfAssignmentResultIsHandled); break; case BoundKind.UnconvertedObjectCreationExpression: @@ -3259,6 +3281,40 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres } return GetRefEscape(assignment.Left, scopeOfTheContainingExpression); + + case BoundKind.Conversion: + Debug.Assert(expr is BoundConversion conversion && + (!conversion.Conversion.IsUserDefined || + conversion.Conversion.Method.HasUnsupportedMetadata || + conversion.Conversion.Method.RefKind == RefKind.None)); + break; + + case BoundKind.UnaryOperator: + Debug.Assert(expr is BoundUnaryOperator unaryOperator && + (unaryOperator.MethodOpt is not { } unaryMethod || + unaryMethod.HasUnsupportedMetadata || + unaryMethod.RefKind == RefKind.None)); + break; + + case BoundKind.BinaryOperator: + Debug.Assert(expr is BoundBinaryOperator binaryOperator && + (binaryOperator.Method is not { } binaryMethod || + binaryMethod.HasUnsupportedMetadata || + binaryMethod.RefKind == RefKind.None)); + break; + + case BoundKind.UserDefinedConditionalLogicalOperator: + Debug.Assert(expr is BoundUserDefinedConditionalLogicalOperator logicalOperator && + (logicalOperator.LogicalOperator.HasUnsupportedMetadata || + logicalOperator.LogicalOperator.RefKind == RefKind.None)); + break; + + case BoundKind.CompoundAssignmentOperator: + Debug.Assert(expr is BoundCompoundAssignmentOperator compoundAssignmentOperator && + (compoundAssignmentOperator.Operator.Method is not { } compoundMethod || + compoundMethod.HasUnsupportedMetadata || + compoundMethod.RefKind == RefKind.None)); + break; } // At this point we should have covered all the possible cases for anything that is not a strict RValue. @@ -3597,6 +3653,37 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF { return CheckRefEscape(node, conversion.Operand, escapeFrom, escapeTo, checkingReceiver, diagnostics); } + + Debug.Assert(!conversion.Conversion.IsUserDefined || + conversion.Conversion.Method.HasUnsupportedMetadata || + conversion.Conversion.Method.RefKind == RefKind.None); + break; + + case BoundKind.UnaryOperator: + Debug.Assert(expr is BoundUnaryOperator unaryOperator && + (unaryOperator.MethodOpt is not { } unaryMethod || + unaryMethod.HasUnsupportedMetadata || + unaryMethod.RefKind == RefKind.None)); + break; + + case BoundKind.BinaryOperator: + Debug.Assert(expr is BoundBinaryOperator binaryOperator && + (binaryOperator.Method is not { } binaryMethod || + binaryMethod.HasUnsupportedMetadata || + binaryMethod.RefKind == RefKind.None)); + break; + + case BoundKind.UserDefinedConditionalLogicalOperator: + Debug.Assert(expr is BoundUserDefinedConditionalLogicalOperator logicalOperator && + (logicalOperator.LogicalOperator.HasUnsupportedMetadata || + logicalOperator.LogicalOperator.RefKind == RefKind.None)); + break; + + case BoundKind.CompoundAssignmentOperator: + Debug.Assert(expr is BoundCompoundAssignmentOperator compoundAssignmentOperator && + (compoundAssignmentOperator.Operator.Method is not { } compoundMethod || + compoundMethod.HasUnsupportedMetadata || + compoundMethod.RefKind == RefKind.None)); break; case BoundKind.ThrowExpression: @@ -3901,7 +3988,22 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres GetValEscape(withExpression.InitializerExpression, scopeOfTheContainingExpression)); case BoundKind.UnaryOperator: - return GetValEscape(((BoundUnaryOperator)expr).Operand, scopeOfTheContainingExpression); + var unaryOperator = (BoundUnaryOperator)expr; + if (unaryOperator.MethodOpt is { } unaryMethod) + { + return GetInvocationEscapeScope( + unaryMethod, + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + unaryMethod.Parameters, + argsOpt: [unaryOperator.Operand], + argRefKindsOpt: default, + argsToParamsOpt: default, + scopeOfTheContainingExpression: scopeOfTheContainingExpression, + isRefEscape: false); + } + + return GetValEscape(unaryOperator.Operand, scopeOfTheContainingExpression); case BoundKind.Conversion: var conversion = (BoundConversion)expr; @@ -3937,6 +4039,23 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres isRefEscape: false); } + if (conversion.Conversion.IsUserDefined) + { + var operatorMethod = conversion.Conversion.Method; + Debug.Assert(operatorMethod is not null); + + return GetInvocationEscapeScope( + operatorMethod, + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + operatorMethod.Parameters, + argsOpt: [conversion.Operand], + argRefKindsOpt: default, + argsToParamsOpt: default, + scopeOfTheContainingExpression: scopeOfTheContainingExpression, + isRefEscape: false); + } + return GetValEscape(conversion.Operand, scopeOfTheContainingExpression); case BoundKind.AssignmentOperator: @@ -3948,12 +4067,40 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres case BoundKind.CompoundAssignmentOperator: var compound = (BoundCompoundAssignmentOperator)expr; + if (compound.Operator.Method is { } compoundMethod) + { + return GetInvocationEscapeScope( + compoundMethod, + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + compoundMethod.Parameters, + argsOpt: [compound.Left, compound.Right], + argRefKindsOpt: default, + argsToParamsOpt: default, + scopeOfTheContainingExpression: scopeOfTheContainingExpression, + isRefEscape: false); + } + return Math.Max(GetValEscape(compound.Left, scopeOfTheContainingExpression), GetValEscape(compound.Right, scopeOfTheContainingExpression)); case BoundKind.BinaryOperator: var binary = (BoundBinaryOperator)expr; + if (binary.Method is { } binaryMethod) + { + return GetInvocationEscapeScope( + binaryMethod, + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + binaryMethod.Parameters, + argsOpt: [binary.Left, binary.Right], + argRefKindsOpt: default, + argsToParamsOpt: default, + scopeOfTheContainingExpression: scopeOfTheContainingExpression, + isRefEscape: false); + } + return Math.Max(GetValEscape(binary.Left, scopeOfTheContainingExpression), GetValEscape(binary.Right, scopeOfTheContainingExpression)); @@ -3966,8 +4113,16 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres case BoundKind.UserDefinedConditionalLogicalOperator: var uo = (BoundUserDefinedConditionalLogicalOperator)expr; - return Math.Max(GetValEscape(uo.Left, scopeOfTheContainingExpression), - GetValEscape(uo.Right, scopeOfTheContainingExpression)); + return GetInvocationEscapeScope( + uo.LogicalOperator, + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + uo.LogicalOperator.Parameters, + argsOpt: [uo.Left, uo.Right], + argRefKindsOpt: default, + argsToParamsOpt: default, + scopeOfTheContainingExpression: scopeOfTheContainingExpression, + isRefEscape: false); case BoundKind.QueryClause: return GetValEscape(((BoundQueryClause)expr).Value, scopeOfTheContainingExpression); @@ -4483,6 +4638,24 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF case BoundKind.UnaryOperator: var unary = (BoundUnaryOperator)expr; + if (unary.MethodOpt is { } unaryMethod) + { + return CheckInvocationEscape( + unary.Syntax, + unaryMethod, + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + unaryMethod.Parameters, + argsOpt: [unary.Operand], + argRefKindsOpt: default, + argsToParamsOpt: default, + checkingReceiver: checkingReceiver, + escapeFrom: escapeFrom, + escapeTo: escapeTo, + diagnostics, + isRefEscape: false); + } + return CheckValEscape(node, unary.Operand, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics); case BoundKind.FromEndIndexExpression: @@ -4530,6 +4703,27 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF isRefEscape: false); } + if (conversion.Conversion.IsUserDefined) + { + var operatorMethod = conversion.Conversion.Method; + Debug.Assert(operatorMethod is not null); + + return CheckInvocationEscape( + conversion.Syntax, + operatorMethod, + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + operatorMethod.Parameters, + argsOpt: [conversion.Operand], + argRefKindsOpt: default, + argsToParamsOpt: default, + checkingReceiver: checkingReceiver, + escapeFrom: escapeFrom, + escapeTo: escapeTo, + diagnostics, + isRefEscape: false); + } + return CheckValEscape(node, conversion.Operand, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics); case BoundKind.AssignmentOperator: @@ -4543,6 +4737,24 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF case BoundKind.CompoundAssignmentOperator: var compound = (BoundCompoundAssignmentOperator)expr; + if (compound.Operator.Method is { } compoundMethod) + { + return CheckInvocationEscape( + compound.Syntax, + compoundMethod, + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + compoundMethod.Parameters, + argsOpt: [compound.Left, compound.Right], + argRefKindsOpt: default, + argsToParamsOpt: default, + checkingReceiver: checkingReceiver, + escapeFrom: escapeFrom, + escapeTo: escapeTo, + diagnostics, + isRefEscape: false); + } + return CheckValEscape(compound.Left.Syntax, compound.Left, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) && CheckValEscape(compound.Right.Syntax, compound.Right, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics); @@ -4554,6 +4766,24 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF return true; } + if (binary.Method is { } binaryMethod) + { + return CheckInvocationEscape( + binary.Syntax, + binaryMethod, + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + binaryMethod.Parameters, + argsOpt: [binary.Left, binary.Right], + argRefKindsOpt: default, + argsToParamsOpt: default, + checkingReceiver: checkingReceiver, + escapeFrom: escapeFrom, + escapeTo: escapeTo, + diagnostics, + isRefEscape: false); + } + return CheckValEscape(binary.Left.Syntax, binary.Left, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) && CheckValEscape(binary.Right.Syntax, binary.Right, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics); @@ -4570,8 +4800,20 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF case BoundKind.UserDefinedConditionalLogicalOperator: var uo = (BoundUserDefinedConditionalLogicalOperator)expr; - return CheckValEscape(uo.Left.Syntax, uo.Left, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) && - CheckValEscape(uo.Right.Syntax, uo.Right, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics); + return CheckInvocationEscape( + uo.Syntax, + uo.LogicalOperator, + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + uo.LogicalOperator.Parameters, + argsOpt: [uo.Left, uo.Right], + argRefKindsOpt: default, + argsToParamsOpt: default, + checkingReceiver: checkingReceiver, + escapeFrom: escapeFrom, + escapeTo: escapeTo, + diagnostics, + isRefEscape: false); case BoundKind.QueryClause: var clauseValue = ((BoundQueryClause)expr).Value; diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index dd5a9adf44764..86c3bbd550cf6 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -17,27 +17,31 @@ namespace Microsoft.CodeAnalysis.CSharp { internal sealed partial class BinderFactory { - private sealed class BinderFactoryVisitor : CSharpSyntaxVisitor + internal sealed class BinderFactoryVisitor : CSharpSyntaxVisitor { private int _position; private CSharpSyntaxNode _memberDeclarationOpt; private Symbol _memberOpt; - private readonly BinderFactory _factory; + private BinderFactory _factory; - internal BinderFactoryVisitor(BinderFactory factory) - { - _factory = factory; - } - - internal void Initialize(int position, CSharpSyntaxNode memberDeclarationOpt, Symbol memberOpt) + internal void Initialize(BinderFactory factory, int position, CSharpSyntaxNode memberDeclarationOpt, Symbol memberOpt) { Debug.Assert((memberDeclarationOpt == null) == (memberOpt == null)); + _factory = factory; _position = position; _memberDeclarationOpt = memberDeclarationOpt; _memberOpt = memberOpt; } + internal void Clear() + { + _factory = null; + _position = 0; + _memberDeclarationOpt = null; + _memberOpt = null; + } + private CSharpCompilation compilation { get diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs index 7b588ae0fa4d1..b71380ca59616 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.cs @@ -17,7 +17,7 @@ internal sealed partial class BinderFactory { // key in the binder cache. // PERF: we are not using ValueTuple because its Equals is relatively slow. - private readonly struct BinderCacheKey : IEquatable + internal readonly struct BinderCacheKey : IEquatable { public readonly CSharpSyntaxNode syntaxNode; public readonly NodeUsage usage; @@ -55,15 +55,17 @@ public override bool Equals(object obj) // In a typing scenario, GetBinder is regularly called with a non-zero position. // This results in a lot of allocations of BinderFactoryVisitors. Pooling them // reduces this churn to almost nothing. + private static readonly ObjectPool s_binderFactoryVisitorPool + = new ObjectPool(static () => new BinderFactoryVisitor(), 64); + private readonly ObjectPool _binderFactoryVisitorPool; - internal BinderFactory(CSharpCompilation compilation, SyntaxTree syntaxTree, bool ignoreAccessibility) + internal BinderFactory(CSharpCompilation compilation, SyntaxTree syntaxTree, bool ignoreAccessibility, ObjectPool binderFactoryVisitorPoolOpt = null) { _compilation = compilation; _syntaxTree = syntaxTree; _ignoreAccessibility = ignoreAccessibility; - - _binderFactoryVisitorPool = new ObjectPool(() => new BinderFactoryVisitor(this), 64); + _binderFactoryVisitorPool = binderFactoryVisitorPoolOpt ?? s_binderFactoryVisitorPool; // 50 is more or less a guess, but it seems to work fine for scenarios that I tried. // we need something big enough to keep binders for most classes and some methods @@ -129,14 +131,27 @@ internal Binder GetBinder(SyntaxNode node, int position, CSharpSyntaxNode member container.AssertMemberExposure(memberOpt); } #endif - BinderFactoryVisitor visitor = _binderFactoryVisitorPool.Allocate(); - visitor.Initialize(position, memberDeclarationOpt, memberOpt); + BinderFactoryVisitor visitor = GetBinderFactoryVisitor(position, memberDeclarationOpt, memberOpt); Binder result = visitor.Visit(node); - _binderFactoryVisitorPool.Free(visitor); + ClearBinderFactoryVisitor(visitor); return result; } + private BinderFactoryVisitor GetBinderFactoryVisitor(int position, CSharpSyntaxNode memberDeclarationOpt, Symbol memberOpt) + { + BinderFactoryVisitor visitor = _binderFactoryVisitorPool.Allocate(); + visitor.Initialize(factory: this, position, memberDeclarationOpt, memberOpt); + + return visitor; + } + + private void ClearBinderFactoryVisitor(BinderFactoryVisitor visitor) + { + visitor.Clear(); + _binderFactoryVisitorPool.Free(visitor); + } + internal InMethodBinder GetPrimaryConstructorInMethodBinder(SynthesizedPrimaryConstructor constructor) { var typeDecl = constructor.GetSyntax(); @@ -159,10 +174,9 @@ internal InMethodBinder GetPrimaryConstructorInMethodBinder(SynthesizedPrimaryCo internal Binder GetInTypeBodyBinder(TypeDeclarationSyntax typeDecl) { - BinderFactoryVisitor visitor = _binderFactoryVisitorPool.Allocate(); - visitor.Initialize(position: typeDecl.SpanStart, memberDeclarationOpt: null, memberOpt: null); + BinderFactoryVisitor visitor = GetBinderFactoryVisitor(position: typeDecl.SpanStart, memberDeclarationOpt: null, memberOpt: null); Binder resultBinder = visitor.VisitTypeDeclarationCore(typeDecl, NodeUsage.NamedTypeBodyOrTypeParameters); - _binderFactoryVisitorPool.Free(visitor); + ClearBinderFactoryVisitor(visitor); return resultBinder; } @@ -174,20 +188,18 @@ internal Binder GetInNamespaceBinder(CSharpSyntaxNode unit) case SyntaxKind.NamespaceDeclaration: case SyntaxKind.FileScopedNamespaceDeclaration: { - BinderFactoryVisitor visitor = _binderFactoryVisitorPool.Allocate(); - visitor.Initialize(0, null, null); + BinderFactoryVisitor visitor = GetBinderFactoryVisitor(0, null, null); Binder result = visitor.VisitNamespaceDeclaration((BaseNamespaceDeclarationSyntax)unit, unit.SpanStart, inBody: true, inUsing: false); - _binderFactoryVisitorPool.Free(visitor); + ClearBinderFactoryVisitor(visitor); return result; } case SyntaxKind.CompilationUnit: // imports are bound by the Script class binder: { - BinderFactoryVisitor visitor = _binderFactoryVisitorPool.Allocate(); - visitor.Initialize(0, null, null); + BinderFactoryVisitor visitor = GetBinderFactoryVisitor(0, null, null); Binder result = visitor.VisitCompilationUnit((CompilationUnitSyntax)unit, inUsing: false, inScript: InScript); - _binderFactoryVisitorPool.Free(visitor); + ClearBinderFactoryVisitor(visitor); return result; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs index fb59362df2573..1170b0c98b125 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs @@ -181,7 +181,7 @@ private BoundAttribute BindAttributeCore(AttributeSyntax node, NamedTypeSymbol a Binder attributeArgumentBinder = this.WithAdditionalFlags(BinderFlags.AttributeArgument); AnalyzedAttributeArguments analyzedArguments = attributeArgumentBinder.BindAttributeArguments(argumentListOpt, attributeTypeForBinding, diagnostics); - ImmutableArray argsToParamsOpt = default; + ImmutableArray argsToParamsOpt; bool expanded = false; BitVector defaultArguments = default; MethodSymbol? attributeConstructor = null; @@ -191,6 +191,7 @@ private BoundAttribute BindAttributeCore(AttributeSyntax node, NamedTypeSymbol a boundConstructorArguments = analyzedArguments.ConstructorArguments.Arguments.SelectAsArray( static (arg, attributeArgumentBinder) => attributeArgumentBinder.BindToTypeForErrorRecovery(arg), attributeArgumentBinder); + argsToParamsOpt = default; } else { @@ -210,12 +211,15 @@ private BoundAttribute BindAttributeCore(AttributeSyntax node, NamedTypeSymbol a if (memberResolutionResult.IsNotNull) { - this.CheckAndCoerceArguments(memberResolutionResult, analyzedArguments.ConstructorArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false); + this.CheckAndCoerceArguments(node, memberResolutionResult, analyzedArguments.ConstructorArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false, out argsToParamsOpt); + } + else + { + argsToParamsOpt = memberResolutionResult.Result.ArgsToParamsOpt; } attributeConstructor = memberResolutionResult.Member; expanded = memberResolutionResult.Resolution == MemberResolutionKind.ApplicableInExpandedForm; - argsToParamsOpt = memberResolutionResult.Result.ArgsToParamsOpt; if (!found) { @@ -229,7 +233,7 @@ private BoundAttribute BindAttributeCore(AttributeSyntax node, NamedTypeSymbol a } else { - attributeArgumentBinder.BindDefaultArgumentsAndParamsCollection( + attributeArgumentBinder.BindDefaultArguments( node, attributeConstructor.Parameters, analyzedArguments.ConstructorArguments.Arguments, diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 6b07dc19faac5..3c0a19894139f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -944,7 +944,7 @@ private bool HasParamsCollectionTypeInProgress(NamedTypeSymbol toCheck) return false; } - internal bool HasCollectionExpressionApplicableAddMethod(SyntaxNode syntax, TypeSymbol targetType, TypeSymbol elementType, out ImmutableArray addMethods, BindingDiagnosticBag diagnostics) + internal bool HasCollectionExpressionApplicableAddMethod(SyntaxNode syntax, TypeSymbol targetType, out ImmutableArray addMethods, BindingDiagnosticBag diagnostics) { Debug.Assert(!targetType.IsDynamic()); @@ -958,7 +958,12 @@ internal bool HasCollectionExpressionApplicableAddMethod(SyntaxNode syntax, Type } var implicitReceiver = new BoundObjectOrCollectionValuePlaceholder(syntax, isNewInstance: true, targetType) { WasCompilerGenerated = true }; - var elementPlaceholder = new BoundValuePlaceholder(syntax, elementType) { WasCompilerGenerated = true }; + + // For the element, we create a dynamic argument and will be forcing overload resolution to convert it to any type. + // This way we are going to do most of the work in terms of determining applicability of 'Add' method candidates + // in overload resolution. + var elementPlaceholder = new BoundValuePlaceholder(syntax, Compilation.DynamicType) { WasCompilerGenerated = true }; + var addMethodBinder = WithAdditionalFlags(BinderFlags.CollectionInitializerAddMethod | BinderFlags.CollectionExpressionConversionValidation); if (namedType is not null) @@ -1047,19 +1052,6 @@ static bool bindInvocationExpression( diagnostics, out addMethods); } - // This is what BindDynamicInvocation is doing in terms of reporting diagnostics and detecting a failure - static bool bindDynamicInvocation( - Binder addMethodBinder, - SyntaxNode node, - AnalyzedArguments arguments, - BindingDiagnosticBag diagnostics) - { - ImmutableArray argArray = addMethodBinder.BuildArgumentsForDynamicInvocation(arguments, diagnostics); - var refKindsArray = arguments.RefKinds.ToImmutableOrNull(); - - return !ReportBadDynamicArguments(node, argArray, refKindsArray, diagnostics, queryClause: null); - } - // This is what BindMethodGroupInvocation is doing in terms of reporting diagnostics and detecting a failure static bool bindMethodGroupInvocation( Binder addMethodBinder, @@ -1070,12 +1062,15 @@ static bool bindMethodGroupInvocation( BindingDiagnosticBag diagnostics, out ImmutableArray addMethods) { + Debug.Assert(methodGroup.ReceiverOpt is not null); + Debug.Assert(methodGroup.ReceiverOpt.Type is not null); + bool result; CompoundUseSiteInfo useSiteInfo = addMethodBinder.GetNewCompoundUseSiteInfo(diagnostics); var resolution = addMethodBinder.ResolveMethodGroup( methodGroup, expression, WellKnownMemberNames.CollectionInitializerAddMethodName, analyzedArguments, useSiteInfo: ref useSiteInfo, - options: (analyzedArguments.HasDynamicArgument ? OverloadResolution.Options.DynamicResolution : OverloadResolution.Options.None)); + options: OverloadResolution.Options.DynamicResolution | OverloadResolution.Options.DynamicConvertsToAnything); diagnostics.Add(expression, useSiteInfo); @@ -1100,10 +1095,11 @@ static bool bindMethodGroupInvocation( } else { + Debug.Assert(resolution.AnalyzedArguments.HasDynamicArgument); + // If overload resolution found one or more applicable methods and at least one argument // was dynamic then treat this as a dynamic call. - if (resolution.AnalyzedArguments.HasDynamicArgument && - resolution.OverloadResolutionResult.HasAnyApplicableMember) + if (resolution.OverloadResolutionResult.HasAnyApplicableMember) { // Note that the runtime binder may consider candidates that haven't passed compile-time final validation // and an ambiguity error may be reported. Also additional checks are performed in runtime final validation @@ -1132,27 +1128,12 @@ static bool bindMethodGroupInvocation( { Debug.Assert(finalApplicableCandidates.Length > 0); - if (resolution.IsExtensionMethodGroup) - { - // error CS1973: 'T' has no applicable method named 'M' but appears to have an - // extension method by that name. Extension methods cannot be dynamically dispatched. Consider - // casting the dynamic arguments or calling the extension method without the extension method - // syntax. - - // We found an extension method, so the instance associated with the method group must have - // existed and had a type. - Debug.Assert(methodGroup.InstanceOpt?.Type is not null); - - Error(diagnostics, ErrorCode.ERR_BadArgTypeDynamicExtension, syntax, methodGroup.InstanceOpt.Type, methodGroup.Name); - addMethods = []; - result = false; - } - else - { - addMethodBinder.ReportDynamicInvocationWarnings(syntax, methodGroup, diagnostics, resolution, finalApplicableCandidates); + addMethods = filterOutBadGenericMethods(addMethodBinder, syntax, methodGroup, analyzedArguments, resolution, finalApplicableCandidates, ref useSiteInfo); + result = !addMethods.IsEmpty; - addMethods = finalApplicableCandidates.SelectAsArray(r => r.Member); - result = bindDynamicInvocation(addMethodBinder, syntax, resolution.AnalyzedArguments, diagnostics); + if (!result) + { + diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingAdd, syntax, methodGroup.ReceiverOpt.Type); } } } @@ -1175,6 +1156,134 @@ static bool bindMethodGroupInvocation( return result; } + static ImmutableArray filterOutBadGenericMethods( + Binder addMethodBinder, SyntaxNode syntax, BoundMethodGroup methodGroup, AnalyzedArguments analyzedArguments, MethodGroupResolution resolution, + ImmutableArray> finalApplicableCandidates, ref CompoundUseSiteInfo useSiteInfo) + { + Debug.Assert(methodGroup.ReceiverOpt is not null); + var resultBuilder = ArrayBuilder.GetInstance(finalApplicableCandidates.Length); + + foreach (var candidate in finalApplicableCandidates) + { + // If the method is generic, skip it if the type arguments cannot be inferred. + var member = candidate.Member; + var typeParameters = member.TypeParameters; + + if (!typeParameters.IsEmpty) + { + if (resolution.IsExtensionMethodGroup) + { + // We need to validate an ability to infer type arguments as well as check conversion to 'this' parameter. + // Overload resolution doesn't check the conversion when 'this' type refers to a type parameter + TypeSymbol? receiverType = methodGroup.ReceiverOpt.Type; + Debug.Assert(receiverType is not null); + bool thisTypeIsOpen = typeParameters.Any((typeParameter, parameter) => parameter.Type.ContainsTypeParameter(typeParameter), member.Parameters[0]); + MethodSymbol? constructed = null; + bool wasFullyInferred = false; + + if (thisTypeIsOpen) + { + constructed = ReducedExtensionMethodSymbol.InferExtensionMethodTypeArguments( + member, receiverType, addMethodBinder.Compilation, ref useSiteInfo, out wasFullyInferred); + } + + if (constructed is null || !wasFullyInferred) + { + // It is quite possible that inference failed because we didn't supply type from the second argument + if (!typeParameters.Any((typeParameter, parameter) => parameter.Type.ContainsTypeParameter(typeParameter), member.Parameters[1])) + { + continue; + } + + // Let's attempt inference with type for the second parameter + // We are going to use the second parameter's type for that + OverloadResolution.GetEffectiveParameterTypes( + member, + argumentCount: 2, + argToParamMap: default, + argumentRefKinds: analyzedArguments.RefKinds, + isMethodGroupConversion: false, + allowRefOmittedArguments: methodGroup.ReceiverOpt.IsExpressionOfComImportType(), + binder: addMethodBinder, + expanded: candidate.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm, + parameterTypes: out ImmutableArray parameterTypes, + parameterRefKinds: out ImmutableArray parameterRefKinds); + + // If we were able to infer something just from the first parameter, + // use partially substituted second type, otherwise inference might fail + // for type parameters "shared" between the parameters. + TypeSymbol secondArgumentType = (constructed ?? member).Parameters[1].Type; + + MethodTypeInferenceResult inferenceResult = MethodTypeInferrer.Infer( + addMethodBinder, + addMethodBinder.Conversions, + member.TypeParameters, + member.ContainingType, + parameterTypes, + parameterRefKinds, + ImmutableArray.Create(methodGroup.ReceiverOpt, new BoundValuePlaceholder(syntax, secondArgumentType) { WasCompilerGenerated = true }), + ref useSiteInfo); + + if (!inferenceResult.Success) + { + continue; + } + + if (thisTypeIsOpen) + { + constructed = member.Construct(inferenceResult.InferredTypeArguments); + } + } + + if (thisTypeIsOpen) + { + Debug.Assert(constructed is not null); + var conversions = constructed.ContainingAssembly.CorLibrary.TypeConversions; + var conversion = conversions.ConvertExtensionMethodThisArg(constructed.Parameters[0].Type, receiverType, ref useSiteInfo); + if (!conversion.Exists) + { + continue; // Conversion to 'this' parameter failed + } + } + } + else if (typeParameters.Any((typeParameter, parameter) => !parameter.Type.ContainsTypeParameter(typeParameter), member.Parameters[0])) + { + // A type parameter does not appear in the parameter type. + continue; + } + } + + resultBuilder.Add(member); + } + + return resultBuilder.ToImmutableAndFree(); + } + + // This is what CanEarlyBindSingleCandidateInvocationWithDynamicArgument is doing in terms of reporting diagnostics and detecting a failure + static bool canEarlyBindSingleCandidateInvocationWithDynamicArgument( + Binder addMethodBinder, + SyntaxNode syntax, + BoundMethodGroup boundMethodGroup, + BindingDiagnosticBag diagnostics, + MethodGroupResolution resolution, + MemberResolutionResult methodResolutionResult, + MethodSymbol singleCandidate) + { + Debug.Assert(boundMethodGroup.TypeArgumentsOpt.IsDefaultOrEmpty); + + if (singleCandidate.IsGenericMethod) + { + return false; + } + + if (addMethodBinder.IsAmbiguousDynamicParamsArgument(resolution.AnalyzedArguments.Arguments, methodResolutionResult, out SyntaxNode argumentSyntax)) + { + return false; + } + + return true; + } + // This is what TryEarlyBindSingleCandidateInvocationWithDynamicArgument is doing in terms of reporting diagnostics and detecting a failure static bool? tryEarlyBindSingleCandidateInvocationWithDynamicArgument( Binder addMethodBinder, @@ -1187,7 +1296,7 @@ static bool bindMethodGroupInvocation( out MethodSymbol? addMethod) { MethodSymbol singleCandidate = methodResolutionResult.LeastOverriddenMember; - if (!addMethodBinder.CanEarlyBindSingleCandidateInvocationWithDynamicArgument(syntax, boundMethodGroup, diagnostics, resolution, methodResolutionResult, singleCandidate)) + if (!canEarlyBindSingleCandidateInvocationWithDynamicArgument(addMethodBinder, syntax, boundMethodGroup, diagnostics, resolution, methodResolutionResult, singleCandidate)) { addMethod = null; return null; @@ -1290,17 +1399,40 @@ static bool bindInvocationExpressionContinued( /// return the argument to the collection initializer Add method or null if the element is not a /// collection initializer node. Otherwise, return the element as is. /// - internal static BoundExpression? GetUnderlyingCollectionExpressionElement(BoundCollectionExpression expr, BoundExpression? element) + internal static BoundExpression GetUnderlyingCollectionExpressionElement(BoundCollectionExpression expr, BoundExpression element, bool throwOnErrors) { if (expr.CollectionTypeKind is CollectionExpressionTypeKind.ImplementsIEnumerable) { - return element switch + switch (element) { - BoundCollectionElementInitializer collectionInitializer => getCollectionInitializerElement(collectionInitializer), - BoundDynamicCollectionElementInitializer dynamicInitializer => dynamicInitializer.Arguments[0], - _ => null, - }; + case BoundCollectionElementInitializer collectionInitializer: + return getCollectionInitializerElement(collectionInitializer); + case BoundDynamicCollectionElementInitializer dynamicInitializer: + return dynamicInitializer.Arguments[0]; + } + + if (throwOnErrors) + { + throw ExceptionUtilities.UnexpectedValue(element); + } + + // Handle error cases from bindCollectionInitializerElementAddMethod. + switch (element) + { + case BoundCall call: + // Overload resolution failed with one or more applicable or ambiguous + // Add methods. This case can be hit for spreads and non-spread elements. + Debug.Assert(call.HasErrors); + Debug.Assert(call.Method.Name == "Add"); + return call.Arguments[call.InvokedAsExtensionMethod ? 1 : 0]; + case BoundBadExpression badExpression: + Debug.Assert(false); // Add test if we hit this assert. + return badExpression; + default: + throw ExceptionUtilities.UnexpectedValue(element); + } } + return element; static BoundExpression getCollectionInitializerElement(BoundCollectionElementInitializer collectionInitializer) @@ -1422,7 +1554,7 @@ private void GenerateImplicitConversionErrorForCollectionExpression( } if (elements.Length > 0 && - !HasCollectionExpressionApplicableAddMethod(node.Syntax, targetType, elementType, addMethods: out _, diagnostics)) + !HasCollectionExpressionApplicableAddMethod(node.Syntax, targetType, addMethods: out _, diagnostics)) { reportedErrors = true; } @@ -1889,10 +2021,6 @@ private BoundExpression CreateMethodGroupConversion(SyntaxNode syntax, BoundExpr { hasErrors = true; } - else if (destination is AnonymousTypeManager.AnonymousDelegatePublicSymbol { CheckParamsCollectionsFeatureAvailability: true }) - { - MessageID.IDS_FeatureParamsCollections.CheckFeatureAvailability(diagnostics, syntax); - } Debug.Assert(conversion.UnderlyingConversions.IsDefault); conversion.MarkUnderlyingConversionsChecked(); @@ -2656,7 +2784,7 @@ private bool MethodGroupConversionHasErrors( return true; } - var sourceMethod = selectedMethod as SourceOrdinaryMethodSymbol; + var sourceMethod = selectedMethod.OriginalDefinition as SourceOrdinaryMethodSymbol; if (sourceMethod is object && sourceMethod.IsPartialWithoutImplementation) { // CS0762: Cannot create delegate from method '{0}' because it is a partial method without an implementing declaration diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 0e9c9f8b4bcb7..9f8e70e818d03 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -128,7 +128,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( var type = boundRHS.Type ?? voidType; return new BoundDeconstructionAssignmentOperator( node, - DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: true), + DeconstructionVariablesAsTuple(left, checkedVariables, assignmentResultTupleType: out _, diagnostics, ignoreDiagnosticsFromTuple: true), new BoundConversion(boundRHS.Syntax, boundRHS, Conversion.Deconstruction, @checked: false, explicitCastInCode: false, conversionGroupOpt: null, constantValueOpt: null, type: type, hasErrors: true), resultIsUsed, @@ -154,9 +154,9 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( FailRemainingInferences(checkedVariables, diagnostics); - var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); + var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, out NamedTypeSymbol returnType, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); Debug.Assert(hasErrors || lhsTuple.Type is object); - TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type!; + returnType = hasErrors ? CreateErrorType() : returnType; var boundConversion = new BoundConversion( boundRHS.Syntax, @@ -316,8 +316,8 @@ private bool MakeDeconstructionConversion( } else { - var single = variable.Single; - Debug.Assert(single is object); + Debug.Assert(variable.Single is object); + var single = AdjustAssignmentTargetForDynamic(variable.Single, forceDynamicResult: out _); Debug.Assert(single.Type is not null); CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); nestedConversion = this.Conversions.ClassifyConversionFromType(tupleOrDeconstructedTypes[i], single.Type, isChecked: CheckOverflowAtRuntime, ref useSiteInfo); @@ -502,7 +502,7 @@ private string GetDebuggerDisplay() if ((object?)variable.Single.Type != null) { // typed-variable on the left - mergedType = variable.Single.Type; + mergedType = AdjustAssignmentTargetForDynamic(variable.Single, forceDynamicResult: out _).Type; } } } @@ -542,11 +542,14 @@ private string GetDebuggerDisplay() syntax: syntax); } - private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder variables, + private BoundTupleExpression DeconstructionVariablesAsTuple( + CSharpSyntaxNode syntax, ArrayBuilder variables, + out NamedTypeSymbol assignmentResultTupleType, BindingDiagnosticBag diagnostics, bool ignoreDiagnosticsFromTuple) { int count = variables.Count; var valuesBuilder = ArrayBuilder.GetInstance(count); + var resultTypesWithAnnotationsBuilder = ArrayBuilder.GetInstance(count); var typesWithAnnotationsBuilder = ArrayBuilder.GetInstance(count); var locationsBuilder = ArrayBuilder.GetInstance(count); var namesBuilder = ArrayBuilder.GetInstance(count); @@ -554,18 +557,24 @@ private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syn foreach (var variable in variables) { BoundExpression value; + TypeSymbol resultType; if (variable.NestedVariables is object) { - value = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, diagnostics, ignoreDiagnosticsFromTuple); + value = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, out NamedTypeSymbol nestedResultType, diagnostics, ignoreDiagnosticsFromTuple); + resultType = nestedResultType; namesBuilder.Add(null); } else { Debug.Assert(variable.Single is object); value = variable.Single; + Debug.Assert(value.Type is not null); + resultType = value.Type; + value = AdjustAssignmentTargetForDynamic(value, forceDynamicResult: out _); namesBuilder.Add(ExtractDeconstructResultElementName(value)); } valuesBuilder.Add(value); + resultTypesWithAnnotationsBuilder.Add(TypeWithAnnotations.Create(resultType)); typesWithAnnotationsBuilder.Add(TypeWithAnnotations.Create(value.Type)); locationsBuilder.Add(variable.Syntax.Location); } @@ -579,14 +588,39 @@ private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syn ImmutableArray inferredPositions = tupleNames.IsDefault ? default : tupleNames.SelectAsArray(n => n != null); bool disallowInferredNames = this.Compilation.LanguageVersion.DisallowInferredTupleElementNames(); + ImmutableArray elementLocations = locationsBuilder.ToImmutableAndFree(); + var createTupleDiagnostics = ignoreDiagnosticsFromTuple ? null : BindingDiagnosticBag.GetInstance(diagnostics); + var type = NamedTypeSymbol.CreateTuple( syntax.Location, - typesWithAnnotationsBuilder.ToImmutableAndFree(), locationsBuilder.ToImmutableAndFree(), + typesWithAnnotationsBuilder.ToImmutableAndFree(), elementLocations, + tupleNames, this.Compilation, + shouldCheckConstraints: createTupleDiagnostics is not null, + includeNullability: false, + errorPositions: disallowInferredNames ? inferredPositions : default, + syntax: syntax, diagnostics: createTupleDiagnostics); + + if (createTupleDiagnostics is { AccumulatesDiagnostics: true, DiagnosticBag: { } bag } && + bag.HasAnyResolvedErrors()) + { + diagnostics.AddRangeAndFree(createTupleDiagnostics); + createTupleDiagnostics = null; // Suppress possibly duplicate errors from CreateTuple call below. + } + + // This type is the same as the 'type' above, or differs only by using 'dynamic' for some elements. + assignmentResultTupleType = NamedTypeSymbol.CreateTuple( + syntax.Location, + resultTypesWithAnnotationsBuilder.ToImmutableAndFree(), elementLocations, tupleNames, this.Compilation, - shouldCheckConstraints: !ignoreDiagnosticsFromTuple, + shouldCheckConstraints: createTupleDiagnostics is not null, includeNullability: false, errorPositions: disallowInferredNames ? inferredPositions : default, - syntax: syntax, diagnostics: ignoreDiagnosticsFromTuple ? null : diagnostics); + syntax: syntax, diagnostics: createTupleDiagnostics); + + if (createTupleDiagnostics is not null) + { + diagnostics.AddRangeAndFree(createTupleDiagnostics); + } return (BoundTupleExpression)BindToNaturalType(new BoundTupleLiteral(syntax, arguments, tupleNames, inferredPositions, type), diagnostics); } @@ -788,12 +822,17 @@ private DeconstructionVariable BindDeconstructionVariables( } default: var boundVariable = BindExpression(node, diagnostics, invoked: false, indexed: false); - var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignable, diagnostics); + var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignable, diagnostics, dynamificationOfAssignmentResultIsHandled: true); + if (expression == null && checkedVariable.Kind != BoundKind.DiscardExpression) { expression = node; } + // This object doesn't escape BindDeconstruction method, we don't call AdjustAssignmentTargetForDynamic + // for checkedVariable here, instead we call it where binder accesses DeconstructionVariable.Single + // In some of the places we need to be able to detect the fact that the type used to be dynamic, and, + // if we erase the fact here, there will be no other place for us to look at. return new DeconstructionVariable(checkedVariable, node); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index d884574b41d96..6129b5278ef13 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -233,10 +233,10 @@ internal NamedTypeSymbol CreateErrorType(string name = "") /// did not meet the requirements, the return value will be a that /// (typically) wraps the subexpression. /// - internal BoundExpression BindValue(ExpressionSyntax node, BindingDiagnosticBag diagnostics, BindValueKind valueKind) + internal BoundExpression BindValue(ExpressionSyntax node, BindingDiagnosticBag diagnostics, BindValueKind valueKind, bool dynamificationOfAssignmentResultIsHandled = false) { var result = this.BindExpression(node, diagnostics: diagnostics, invoked: false, indexed: false); - return CheckValue(result, valueKind, diagnostics); + return CheckValue(result, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled); } internal BoundExpression BindRValueWithoutTargetType(ExpressionSyntax node, BindingDiagnosticBag diagnostics, bool reportNoTargetType = true) @@ -3313,14 +3313,19 @@ private BoundExpression BindArgumentExpression(BindingDiagnosticBag diagnostics, #nullable enable private void CheckAndCoerceArguments( + SyntaxNode node, MemberResolutionResult methodResult, AnalyzedArguments analyzedArguments, BindingDiagnosticBag diagnostics, BoundExpression? receiver, - bool invokedAsExtensionMethod) + bool invokedAsExtensionMethod, + out ImmutableArray argsToParamsOpt) where TMember : Symbol { var result = methodResult.Result; + bool expanded = result.Kind == MemberResolutionKind.ApplicableInExpandedForm; + int firstParamsArgument = -1; + ArrayBuilder? paramsArgsBuilder = null; var arguments = analyzedArguments.Arguments; // Parameter types should be taken from the least overridden member: @@ -3328,10 +3333,17 @@ private void CheckAndCoerceArguments( for (int arg = 0; arg < arguments.Count; ++arg) { - var kind = result.ConversionForArg(arg); BoundExpression argument = arguments[arg]; - if (argument is not BoundArgListOperator && !argument.HasAnyErrors) + if (argument is BoundArgListOperator) + { + Debug.Assert(result.ConversionForArg(arg).IsIdentity); + Debug.Assert(!argument.NeedsToBeConverted()); + Debug.Assert(!expanded || result.ParameterFromArgument(arg) != parameters.Length - 1); + continue; + } + + if (!argument.HasAnyErrors) { var argRefKind = analyzedArguments.RefKind(arg); @@ -3340,7 +3352,7 @@ private void CheckAndCoerceArguments( // Disallow using `ref readonly` parameters with no or `in` argument modifier, // same as older versions of the compiler would (since they would see the parameter as `ref`). if (argRefKind is RefKind.None or RefKind.In && - GetCorrespondingParameter(ref result, parameters, arg).RefKind == RefKind.RefReadOnlyParameter) + getCorrespondingParameter(in result, parameters, arg).RefKind == RefKind.RefReadOnlyParameter) { var available = CheckFeatureAvailability(argument.Syntax, MessageID.IDS_FeatureRefReadonlyParameters, diagnostics); Debug.Assert(!available); @@ -3353,7 +3365,7 @@ private void CheckAndCoerceArguments( // Warn for `ref`/`in` or None/`ref readonly` mismatch. if (argRefKind == RefKind.Ref) { - if (GetCorrespondingParameter(ref result, parameters, arg).RefKind == RefKind.In) + if (getCorrespondingParameter(in result, parameters, arg).RefKind == RefKind.In) { Debug.Assert(argNumber > 0); // The 'ref' modifier for argument {0} corresponding to 'in' parameter is equivalent to 'in'. Consider using 'in' instead. @@ -3364,7 +3376,7 @@ private void CheckAndCoerceArguments( } } else if (argRefKind == RefKind.None && - GetCorrespondingParameter(ref result, parameters, arg).RefKind == RefKind.RefReadOnlyParameter) + getCorrespondingParameter(in result, parameters, arg).RefKind == RefKind.RefReadOnlyParameter) { if (!this.CheckValueKind(argument.Syntax, argument, BindValueKind.RefersToLocation, checkingReceiver: false, BindingDiagnosticBag.Discarded)) { @@ -3399,50 +3411,195 @@ private void CheckAndCoerceArguments( } } + int paramNum = result.ParameterFromArgument(arg); + + if (expanded && paramNum == parameters.Length - 1) + { + Debug.Assert(paramsArgsBuilder is null); + firstParamsArgument = arg; + paramsArgsBuilder = collectParamsArgs(in methodResult, parameters, arguments, ref arg, diagnostics); + continue; + } + + arguments[arg] = coerceArgument(in methodResult, receiver, parameters, argumentsForInterpolationConversion: arguments, argument, arg, parameters[paramNum].TypeWithAnnotations, diagnostics); + } + + argsToParamsOpt = result.ArgsToParamsOpt; + + if (paramsArgsBuilder is not null) + { + // Note, this call is going to free paramsArgsBuilder + createParamsCollection(node, in methodResult, receiver, parameters, analyzedArguments, firstParamsArgument, paramsArgsBuilder, ref argsToParamsOpt, diagnostics); + } + + Debug.Assert(analyzedArguments.RefKinds.Count == 0 || analyzedArguments.RefKinds.Count == arguments.Count); + Debug.Assert(analyzedArguments.Names.Count == 0 || analyzedArguments.Names.Count == arguments.Count); + Debug.Assert(argsToParamsOpt.IsDefault || argsToParamsOpt.Length == arguments.Count); + + result.ArgumentsWereCoerced(); + return; + + BoundExpression coerceArgument( + in MemberResolutionResult methodResult, + BoundExpression? receiver, + ImmutableArray parameters, + ArrayBuilder? argumentsForInterpolationConversion, + BoundExpression argument, + int arg, + TypeWithAnnotations parameterTypeWithAnnotations, + BindingDiagnosticBag diagnostics) + { + var result = methodResult.Result; + var kind = result.ConversionForArg(arg); + BoundExpression coercedArgument = argument; + if (kind.IsInterpolatedStringHandler) { Debug.Assert(argument is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true }); - TypeWithAnnotations parameterTypeWithAnnotations = GetCorrespondingParameterTypeWithAnnotations(ref result, parameters, arg); reportUnsafeIfNeeded(methodResult, diagnostics, argument, parameterTypeWithAnnotations); - arguments[arg] = BindInterpolatedStringHandlerInMemberCall(argument, parameterTypeWithAnnotations.Type, arguments, parameters, ref result, arg, receiver, diagnostics); + coercedArgument = bindInterpolatedStringHandlerInMemberCall(argument, parameterTypeWithAnnotations.Type, argumentsForInterpolationConversion, parameters, in result, arg, receiver, diagnostics); } // https://github.com/dotnet/roslyn/issues/37119 : should we create an (Identity) conversion when the kind is Identity but the types differ? else if (!kind.IsIdentity) { - TypeWithAnnotations parameterTypeWithAnnotations = GetCorrespondingParameterTypeWithAnnotations(ref result, parameters, arg); reportUnsafeIfNeeded(methodResult, diagnostics, argument, parameterTypeWithAnnotations); - arguments[arg] = CreateConversion(argument.Syntax, argument, kind, isCast: false, conversionGroupOpt: null, parameterTypeWithAnnotations.Type, diagnostics); + coercedArgument = CreateConversion(argument.Syntax, argument, kind, isCast: false, conversionGroupOpt: null, parameterTypeWithAnnotations.Type, diagnostics); } else if (argument.Kind == BoundKind.OutVariablePendingInference) { - TypeWithAnnotations parameterTypeWithAnnotations = GetCorrespondingParameterTypeWithAnnotations(ref result, parameters, arg); - arguments[arg] = ((OutVariablePendingInference)argument).SetInferredTypeWithAnnotations(parameterTypeWithAnnotations, diagnostics); + coercedArgument = ((OutVariablePendingInference)argument).SetInferredTypeWithAnnotations(parameterTypeWithAnnotations, diagnostics); } else if (argument.Kind == BoundKind.OutDeconstructVarPendingInference) { - TypeWithAnnotations parameterTypeWithAnnotations = GetCorrespondingParameterTypeWithAnnotations(ref result, parameters, arg); - arguments[arg] = ((OutDeconstructVarPendingInference)argument).SetInferredTypeWithAnnotations(parameterTypeWithAnnotations, success: true); + coercedArgument = ((OutDeconstructVarPendingInference)argument).SetInferredTypeWithAnnotations(parameterTypeWithAnnotations, success: true); } else if (argument.Kind == BoundKind.DiscardExpression && !argument.HasExpressionType()) { - TypeWithAnnotations parameterTypeWithAnnotations = GetCorrespondingParameterTypeWithAnnotations(ref result, parameters, arg); Debug.Assert(parameterTypeWithAnnotations.HasType); - arguments[arg] = ((BoundDiscardExpression)argument).SetInferredTypeWithAnnotations(parameterTypeWithAnnotations); + coercedArgument = ((BoundDiscardExpression)argument).SetInferredTypeWithAnnotations(parameterTypeWithAnnotations); } else if (argument.NeedsToBeConverted()) { Debug.Assert(kind.IsIdentity); if (argument is BoundTupleLiteral) { - TypeWithAnnotations parameterTypeWithAnnotations = GetCorrespondingParameterTypeWithAnnotations(ref result, parameters, arg); // CreateConversion reports tuple literal name mismatches, and constructs the expected pattern of bound nodes. - arguments[arg] = CreateConversion(argument.Syntax, argument, kind, isCast: false, conversionGroupOpt: null, parameterTypeWithAnnotations.Type, diagnostics); + coercedArgument = CreateConversion(argument.Syntax, argument, kind, isCast: false, conversionGroupOpt: null, parameterTypeWithAnnotations.Type, diagnostics); } else { - arguments[arg] = BindToNaturalType(argument, diagnostics); + coercedArgument = BindToNaturalType(argument, diagnostics); + } + } + + return coercedArgument; + } + + static ArrayBuilder collectParamsArgs( + in MemberResolutionResult methodResult, + ImmutableArray parameters, + ArrayBuilder arguments, + ref int arg, + BindingDiagnosticBag diagnostics) + { + var result = methodResult.Result; + var paramsArgsBuilder = ArrayBuilder.GetInstance(); + int paramsIndex = parameters.Length - 1; + + while (true) + { + Debug.Assert(arguments[arg].Kind is not + (BoundKind.OutVariablePendingInference or BoundKind.OutDeconstructVarPendingInference or BoundKind.DiscardExpression or BoundKind.ArgListOperator)); + + // Conversions to elements of collection are applied in the process of collection construction + paramsArgsBuilder.Add(arguments[arg]); + + if (arg + 1 == arguments.Count || result.ParameterFromArgument(arg + 1) != paramsIndex) + { + break; } + + arg++; + } + + return paramsArgsBuilder; + } + + // Note, this function is going to free paramsArgsBuilder + void createParamsCollection( + SyntaxNode node, + in MemberResolutionResult methodResult, + BoundExpression? receiver, + ImmutableArray parameters, + AnalyzedArguments analyzedArguments, + int firstParamsArgument, + ArrayBuilder paramsArgsBuilder, + ref ImmutableArray argsToParamsOpt, + BindingDiagnosticBag diagnostics) + { + Debug.Assert(methodResult.Result.ParamsElementTypeOpt.HasType); + Debug.Assert(methodResult.Result.ParamsElementTypeOpt.Type != (object)ErrorTypeSymbol.EmptyParamsCollectionElementTypeSentinel); + + int paramsIndex = parameters.Length - 1; + + if (parameters[paramsIndex].Type.IsSZArray()) + { + var result = methodResult.Result; + TypeWithAnnotations paramsElementTypeOpt = result.ParamsElementTypeOpt; + + for (int i = 0; i < paramsArgsBuilder.Count; i++) + { + paramsArgsBuilder[i] = coerceArgument( + in methodResult, receiver, parameters, + argumentsForInterpolationConversion: null, // We do not use arguments for interpolations as param array elements + paramsArgsBuilder[i], + arg: firstParamsArgument + i, + paramsElementTypeOpt, + diagnostics); + } + } + + ImmutableArray collectionArgs = paramsArgsBuilder.ToImmutableAndFree(); + Debug.Assert(collectionArgs.Length != 0); + + BoundExpression collection = CreateParamsCollection(node, parameters[paramsIndex], collectionArgs, diagnostics); + var arguments = analyzedArguments.Arguments; + + Debug.Assert(firstParamsArgument != -1); + Debug.Assert(collectionArgs.Length == 1 || firstParamsArgument + collectionArgs.Length == arguments.Count); + + ArrayBuilder? argsToParamsBuilder = null; + if (!argsToParamsOpt.IsDefault && collectionArgs.Length > 1) + { + argsToParamsBuilder = ArrayBuilder.GetInstance(argsToParamsOpt.Length); + argsToParamsBuilder.AddRange(argsToParamsOpt); + } + + for (var i = firstParamsArgument + collectionArgs.Length - 1; i != firstParamsArgument; i--) + { + arguments.RemoveAt(i); + + Debug.Assert(argsToParamsBuilder is not null || argsToParamsOpt.IsDefault); + argsToParamsBuilder?.RemoveAt(i); + + if (analyzedArguments.RefKinds is { Count: > 0 } refKindsBuilder) + { + refKindsBuilder.RemoveAt(i); + } + + if (analyzedArguments.Names is { Count: > 0 } namesBuilder) + { + namesBuilder.RemoveAt(i); + } + } + + arguments[firstParamsArgument] = collection; + + if (argsToParamsBuilder is object) + { + argsToParamsOpt = argsToParamsBuilder.ToImmutableOrNull(); + argsToParamsBuilder.Free(); } } @@ -3456,28 +3613,238 @@ void reportUnsafeIfNeeded(MemberResolutionResult methodResult, BindingD //CONSIDER: Return a bad expression so that HasErrors is true? } } - } - private static ParameterSymbol GetCorrespondingParameter(ref MemberAnalysisResult result, ImmutableArray parameters, int arg) - { - int paramNum = result.ParameterFromArgument(arg); - return parameters[paramNum]; - } -#nullable disable - - private static TypeWithAnnotations GetCorrespondingParameterTypeWithAnnotations(ref MemberAnalysisResult result, ImmutableArray parameters, int arg) - { - int paramNum = result.ParameterFromArgument(arg); - - if (paramNum == parameters.Length - 1 && result.Kind == MemberResolutionKind.ApplicableInExpandedForm) + static ParameterSymbol getCorrespondingParameter(in MemberAnalysisResult result, ImmutableArray parameters, int arg) { - Debug.Assert(result.ParamsElementTypeOpt.HasType); - Debug.Assert(result.ParamsElementTypeOpt.Type != (object)ErrorTypeSymbol.EmptyParamsCollectionElementTypeSentinel); - return result.ParamsElementTypeOpt; + int paramNum = result.ParameterFromArgument(arg); + return parameters[paramNum]; } - return parameters[paramNum].TypeWithAnnotations; + BoundExpression bindInterpolatedStringHandlerInMemberCall( + BoundExpression unconvertedString, + TypeSymbol handlerType, + ArrayBuilder? arguments, + ImmutableArray parameters, + in MemberAnalysisResult memberAnalysisResult, + int interpolatedStringArgNum, + BoundExpression? receiver, + BindingDiagnosticBag diagnostics) + { + Debug.Assert(unconvertedString is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true }); + var interpolatedStringConversion = memberAnalysisResult.ConversionForArg(interpolatedStringArgNum); + Debug.Assert(interpolatedStringConversion.IsInterpolatedStringHandler); + Debug.Assert(handlerType is NamedTypeSymbol { IsInterpolatedStringHandlerType: true }); + + var correspondingParameter = getCorrespondingParameter(in memberAnalysisResult, parameters, interpolatedStringArgNum); + var handlerParameterIndexes = correspondingParameter.InterpolatedStringHandlerArgumentIndexes; + + if (memberAnalysisResult.Kind == MemberResolutionKind.ApplicableInExpandedForm && correspondingParameter.Ordinal == parameters.Length - 1) + { + Debug.Assert(handlerParameterIndexes.IsEmpty); + + // No arguments, fall back to the standard conversion steps. + return CreateConversion( + unconvertedString.Syntax, + unconvertedString, + interpolatedStringConversion, + isCast: false, + conversionGroupOpt: null, + handlerType, + diagnostics); + } + + Debug.Assert(arguments is not null); + + if (correspondingParameter.HasInterpolatedStringHandlerArgumentError) + { + // The InterpolatedStringHandlerArgumentAttribute applied to parameter '{0}' is malformed and cannot be interpreted. Construct an instance of '{1}' manually. + diagnostics.Add(ErrorCode.ERR_InterpolatedStringHandlerArgumentAttributeMalformed, unconvertedString.Syntax.Location, correspondingParameter, handlerType); + return CreateConversion( + unconvertedString.Syntax, + unconvertedString, + interpolatedStringConversion, + isCast: false, + conversionGroupOpt: null, + wasCompilerGenerated: false, + handlerType, + diagnostics, + hasErrors: true); + } + + if (handlerParameterIndexes.IsEmpty) + { + // No arguments, fall back to the standard conversion steps. + return CreateConversion( + unconvertedString.Syntax, + unconvertedString, + interpolatedStringConversion, + isCast: false, + conversionGroupOpt: null, + handlerType, + diagnostics); + } + + Debug.Assert(handlerParameterIndexes.All((index, paramLength) => index >= BoundInterpolatedStringArgumentPlaceholder.InstanceParameter && index < paramLength, + parameters.Length)); + + // We need to find the appropriate argument expression for every expected parameter, and error on any that occur after the current parameter + + ImmutableArray handlerArgumentIndexes; + + if (memberAnalysisResult.ArgsToParamsOpt.IsDefault && arguments.Count == parameters.Length) + { + // No parameters are missing and no remapped indexes, we can just use the original indexes + handlerArgumentIndexes = handlerParameterIndexes; + } + else + { + // Args and parameters were reordered via named parameters, or parameters are missing. Find the correct argument index for each parameter. + var handlerArgumentIndexesBuilder = ArrayBuilder.GetInstance(handlerParameterIndexes.Length, fillWithValue: BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter); + for (int handlerParameterIndex = 0; handlerParameterIndex < handlerParameterIndexes.Length; handlerParameterIndex++) + { + int handlerParameter = handlerParameterIndexes[handlerParameterIndex]; + Debug.Assert(handlerArgumentIndexesBuilder[handlerParameterIndex] is BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter); + + if (handlerParameter == BoundInterpolatedStringArgumentPlaceholder.InstanceParameter) + { + handlerArgumentIndexesBuilder[handlerParameterIndex] = handlerParameter; + continue; + } + + for (int argumentIndex = 0; argumentIndex < arguments.Count; argumentIndex++) + { + // The index in the original parameter list we're looking to match up. + int argumentParameterIndex = memberAnalysisResult.ParameterFromArgument(argumentIndex); + // Is the original parameter index of the current argument the parameter index that was specified in the attribute? + if (argumentParameterIndex == handlerParameter) + { + // We can't just bail out on the first match: users can duplicate parameters in attributes, causing the same value to be passed twice. + handlerArgumentIndexesBuilder[handlerParameterIndex] = argumentIndex; + } + } + } + + handlerArgumentIndexes = handlerArgumentIndexesBuilder.ToImmutableAndFree(); + } + + var argumentPlaceholdersBuilder = ArrayBuilder.GetInstance(handlerArgumentIndexes.Length); + var argumentRefKindsBuilder = ArrayBuilder.GetInstance(handlerArgumentIndexes.Length); + bool hasErrors = false; + + // Now, go through all the specified arguments and see if any were specified _after_ the interpolated string, and construct + // a set of placeholders for overload resolution. + for (int i = 0; i < handlerArgumentIndexes.Length; i++) + { + int argumentIndex = handlerArgumentIndexes[i]; + Debug.Assert(argumentIndex != interpolatedStringArgNum); + + RefKind refKind; + TypeSymbol placeholderType; + switch (argumentIndex) + { + case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter: + Debug.Assert(receiver!.Type is not null); + refKind = RefKind.None; + placeholderType = receiver.Type; + break; + case BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter: + { + // Don't error if the parameter isn't optional or params: the user will already have an error for missing an optional parameter or overload resolution failed. + // If it is optional, then they could otherwise not specify the parameter and that's an error + var originalParameterIndex = handlerParameterIndexes[i]; + var parameter = parameters[originalParameterIndex]; + if (parameter.IsOptional || + (memberAnalysisResult.Kind == MemberResolutionKind.ApplicableInExpandedForm && originalParameterIndex + 1 == parameters.Length)) + { + // Parameter '{0}' is not explicitly provided, but is used as an argument to the interpolated string handler conversion on parameter '{1}'. Specify the value of '{0}' before '{1}'. + diagnostics.Add( + ErrorCode.ERR_InterpolatedStringHandlerArgumentOptionalNotSpecified, + unconvertedString.Syntax.Location, + parameter.Name, + correspondingParameter.Name); + hasErrors = true; + } + + refKind = parameter.RefKind; + placeholderType = parameter.Type; + } + break; + default: + { + var originalParameterIndex = handlerParameterIndexes[i]; + var parameter = parameters[originalParameterIndex]; + if (argumentIndex > interpolatedStringArgNum) + { + // Parameter '{0}' is an argument to the interpolated string handler conversion on parameter '{1}', but the corresponding argument is specified after the interpolated string expression. Reorder the arguments to move '{0}' before '{1}'. + diagnostics.Add( + ErrorCode.ERR_InterpolatedStringHandlerArgumentLocatedAfterInterpolatedString, + arguments[argumentIndex].Syntax.Location, + parameter.Name, + correspondingParameter.Name); + hasErrors = true; + } + + refKind = parameter.RefKind; + placeholderType = parameter.Type; + } + break; + } + + SyntaxNode placeholderSyntax; + bool isSuppressed; + + switch (argumentIndex) + { + case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter: + Debug.Assert(receiver != null); + isSuppressed = receiver.IsSuppressed; + placeholderSyntax = receiver.Syntax; + break; + case BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter: + placeholderSyntax = unconvertedString.Syntax; + isSuppressed = false; + break; + case >= 0: + placeholderSyntax = arguments[argumentIndex].Syntax; + isSuppressed = arguments[argumentIndex].IsSuppressed; + break; + default: + throw ExceptionUtilities.UnexpectedValue(argumentIndex); + } + + argumentPlaceholdersBuilder.Add( + (BoundInterpolatedStringArgumentPlaceholder)(new BoundInterpolatedStringArgumentPlaceholder( + placeholderSyntax, + argumentIndex, + placeholderType, + hasErrors: argumentIndex == BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter) + { WasCompilerGenerated = true }.WithSuppression(isSuppressed))); + // We use the parameter refkind, rather than what the argument was actually passed with, because that will suppress duplicated errors + // about arguments being passed with the wrong RefKind. The user will have already gotten an error about mismatched RefKinds or it will + // be a place where refkinds are allowed to differ + argumentRefKindsBuilder.Add(refKind == RefKind.RefReadOnlyParameter ? RefKind.In : refKind); + } + + var interpolatedString = BindUnconvertedInterpolatedExpressionToHandlerType( + unconvertedString, + (NamedTypeSymbol)handlerType, + diagnostics, + additionalConstructorArguments: argumentPlaceholdersBuilder.ToImmutableAndFree(), + additionalConstructorRefKinds: argumentRefKindsBuilder.ToImmutableAndFree()); + + return new BoundConversion( + interpolatedString.Syntax, + interpolatedString, + interpolatedStringConversion, + @checked: CheckOverflowAtRuntime, + explicitCastInCode: false, + conversionGroupOpt: null, + constantValueOpt: null, + handlerType, + hasErrors || interpolatedString.HasErrors); + } } +#nullable disable private BoundExpression BindArrayCreationExpression(ArrayCreationExpressionSyntax node, BindingDiagnosticBag diagnostics) { @@ -4528,9 +4895,15 @@ private BoundExpression BindConstructorInitializerCoreContinued( { ReportConstructorUseSiteDiagnostics(errorLocation, diagnostics, suppressUnsupportedRequiredMembersError: true, in overloadResolutionUseSiteInfo); + ImmutableArray argsToParamsOpt; + if (memberResolutionResult.IsNotNull) { - this.CheckAndCoerceArguments(memberResolutionResult, analyzedArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false); + this.CheckAndCoerceArguments(nonNullSyntax, memberResolutionResult, analyzedArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false, out argsToParamsOpt); + } + else + { + argsToParamsOpt = memberResolutionResult.Result.ArgsToParamsOpt; } NamedTypeSymbol baseType = constructor.ContainingType.BaseTypeNoUseSiteDiagnostics; @@ -4569,7 +4942,6 @@ private BoundExpression BindConstructorInitializerCoreContinued( ReportDiagnosticsIfObsolete(diagnostics, resultMember, nonNullSyntax, hasBaseReceiver: isBaseConstructorInitializer); var expanded = memberResolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; - var argsToParamsOpt = memberResolutionResult.Result.ArgsToParamsOpt; if (constructor is SynthesizedPrimaryConstructor primaryConstructor) { @@ -4607,7 +4979,7 @@ private BoundExpression BindConstructorInitializerCoreContinued( primaryConstructor.SetParametersPassedToTheBase(parametersPassedToBase); } - BindDefaultArgumentsAndParamsCollection(nonNullSyntax, resultMember.Parameters, analyzedArguments.Arguments, analyzedArguments.RefKinds, analyzedArguments.Names, ref argsToParamsOpt, out var defaultArguments, expanded, enableCallerInfo, diagnostics); + BindDefaultArguments(nonNullSyntax, resultMember.Parameters, analyzedArguments.Arguments, analyzedArguments.RefKinds, analyzedArguments.Names, ref argsToParamsOpt, out var defaultArguments, expanded, enableCallerInfo, diagnostics); var arguments = analyzedArguments.Arguments.ToImmutable(); var refKinds = analyzedArguments.RefKinds.ToImmutableOrNull(); @@ -5323,7 +5695,7 @@ private BoundExpression BindObjectInitializerMember( { // D = { ..., = , ... }, where D : dynamic boundMember = new BoundDynamicObjectInitializerMember(leftSyntax, memberName.Identifier.Text, implicitReceiver.Type, initializerType, hasErrors: false); - return CheckValue(boundMember, valueKind, diagnostics); + return CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); } else { @@ -5426,7 +5798,15 @@ private BoundExpression BindObjectInitializerMember( case BoundKind.IndexerAccess: { - var indexer = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)boundMember, valueKind, diagnostics); + var indexer = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); + + Debug.Assert(valueKind is BindValueKind.RValue or BindValueKind.RefAssignable or BindValueKind.Assignable); + + if (valueKind == BindValueKind.Assignable) + { + indexer = (BoundIndexerAccess)AdjustAssignmentTargetForDynamic(indexer, forceDynamicResult: out _); + } + boundMember = indexer; hasErrors |= isRhsNestedInitializer && !CheckNestedObjectInitializerPropertySymbol(indexer.Indexer, leftSyntax, diagnostics, hasErrors, ref resultKind); arguments = indexer.Arguments; @@ -5466,7 +5846,10 @@ private BoundExpression BindObjectInitializerMember( hasErrors |= !CheckNestedObjectInitializerPropertySymbol(property, leftSyntax, diagnostics, hasErrors, ref resultKind); } - return hasErrors ? boundMember : CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + return hasErrors ? + boundMember : + CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); case BoundKind.DynamicObjectInitializerMember: break; @@ -5483,7 +5866,8 @@ private BoundExpression BindObjectInitializerMember( case BoundKind.ArrayAccess: case BoundKind.PointerElementAccess: - return CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + return CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); default: return BadObjectInitializerMemberAccess(boundMember, implicitReceiver, leftSyntax, diagnostics, valueKind, hasErrors); @@ -5568,7 +5952,8 @@ private BoundExpression BadObjectInitializerMemberAccess( break; case LookupResultKind.Inaccessible: - boundMember = CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + boundMember = CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); Debug.Assert(boundMember.HasAnyErrors); break; @@ -5925,7 +6310,7 @@ boundElementInitializerExpressions[0] is not var d = BindingDiagnosticBag.GetInstance(); // This assert provides some validation that, if the real invocation binding succeeds, then the HasCollectionExpressionApplicableAddMethod helper succeeds as well. - Debug.Assert(collectionInitializerAddMethodBinder.HasCollectionExpressionApplicableAddMethod(elementInitializer, implicitReceiver.Type, boundElementInitializerExpressions[0].Type, addMethods: out _, d)); + Debug.Assert(collectionInitializerAddMethodBinder.HasCollectionExpressionApplicableAddMethod(elementInitializer, implicitReceiver.Type, addMethods: out _, d)); d.Free(); } @@ -5946,7 +6331,7 @@ BoundExpression bindCollectionInitializerElementAddMethod( if (implicitReceiver.Type.IsDynamic()) { - var hasErrors = ReportBadDynamicArguments(elementInitializer, boundElementInitializerExpressions, refKinds: default, diagnostics, queryClause: null); + var hasErrors = ReportBadDynamicArguments(elementInitializer, implicitReceiver, boundElementInitializerExpressions, refKinds: default, diagnostics, queryClause: null); return new BoundDynamicCollectionElementInitializer( elementInitializer, @@ -6215,6 +6600,7 @@ protected BoundExpression BindClassCreationExpression( if (result == null) { if (finalApplicableCandidates.Length != 1 && + Compilation.LanguageVersion > LanguageVersion.CSharp12 && // The following check (while correct) is redundant otherwise HasApplicableMemberWithPossiblyExpandedNonArrayParamsCollection(analyzedArguments.Arguments, finalApplicableCandidates)) { Error(diagnostics, @@ -6225,7 +6611,7 @@ protected BoundExpression BindClassCreationExpression( var argArray = BuildArgumentsForDynamicInvocation(analyzedArguments, diagnostics); var refKindsArray = analyzedArguments.RefKinds.ToImmutableOrNull(); - hasErrors &= ReportBadDynamicArguments(node, argArray, refKindsArray, diagnostics, queryClause: null); + hasErrors &= ReportBadDynamicArguments(node, receiver: null, argArray, refKindsArray, diagnostics, queryClause: null); BoundObjectInitializerExpressionBase boundInitializerOpt; boundInitializerOpt = MakeBoundInitializerOpt(typeNode, type, initializerSyntaxOpt, initializerTypeOpt, diagnostics); @@ -6295,9 +6681,15 @@ private BoundObjectCreationExpression BindClassCreationExpressionContinued( ReportConstructorUseSiteDiagnostics(typeNode.Location, diagnostics, suppressUnsupportedRequiredMembersError: false, in overloadResolutionUseSiteInfo); + ImmutableArray argToParams; + if (memberResolutionResult.IsNotNull) { - this.CheckAndCoerceArguments(memberResolutionResult, analyzedArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false); + this.CheckAndCoerceArguments(node, memberResolutionResult, analyzedArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false, out argToParams); + } + else + { + argToParams = memberResolutionResult.Result.ArgsToParamsOpt; } var method = memberResolutionResult.Member; @@ -6323,8 +6715,7 @@ private BoundObjectCreationExpression BindClassCreationExpressionContinued( null; var expanded = memberResolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; - var argToParams = memberResolutionResult.Result.ArgsToParamsOpt; - BindDefaultArgumentsAndParamsCollection(node, method.Parameters, analyzedArguments.Arguments, analyzedArguments.RefKinds, analyzedArguments.Names, ref argToParams, out var defaultArguments, expanded, enableCallerInfo: true, diagnostics); + BindDefaultArguments(node, method.Parameters, analyzedArguments.Arguments, analyzedArguments.RefKinds, analyzedArguments.Names, ref argToParams, out var defaultArguments, expanded, enableCallerInfo: true, diagnostics); var arguments = analyzedArguments.Arguments.ToImmutable(); var refKinds = analyzedArguments.RefKinds.ToImmutableOrNull(); @@ -6375,7 +6766,7 @@ private BoundExpression CreateBadClassCreationExpression( if (memberResolutionResult.IsNotNull) { - this.CheckAndCoerceArguments(memberResolutionResult, analyzedArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false); + this.CheckAndCoerceArguments(node, memberResolutionResult, analyzedArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false, argsToParamsOpt: out _); } LookupResultKind resultKind; @@ -7911,7 +8302,8 @@ protected MethodGroupResolution BindExtensionMethod( OverloadResolution.Options.IsFunctionPointerResolution | OverloadResolution.Options.InferWithDynamic | OverloadResolution.Options.IgnoreNormalFormIfHasValidParamsParameter | - OverloadResolution.Options.DynamicResolution)) == 0); + OverloadResolution.Options.DynamicResolution | + OverloadResolution.Options.DynamicConvertsToAnything)) == 0); var firstResult = new MethodGroupResolution(); AnalyzedArguments actualArguments = null; @@ -9273,7 +9665,7 @@ private BoundExpression BindDynamicIndexer( var argArray = BuildArgumentsForDynamicInvocation(arguments, diagnostics); var refKindsArray = arguments.RefKinds.ToImmutableOrNull(); - hasErrors &= ReportBadDynamicArguments(syntax, argArray, refKindsArray, diagnostics, queryClause: null); + hasErrors &= ReportBadDynamicArguments(syntax, receiver, argArray, refKindsArray, diagnostics, queryClause: null); return new BoundDynamicIndexerAccess( syntax, @@ -9333,17 +9725,19 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( argumentSyntax, singleCandidate); } } - else + // For C# 12 and earlier always bind at runtime. + else if (Compilation.LanguageVersion > LanguageVersion.CSharp12) { var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); resultWithSingleCandidate.ResultsBuilder.Add(finalApplicableCandidates[0]); overloadResolutionResult.Free(); - return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, resultWithSingleCandidate, diagnostics); + return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, resultWithSingleCandidate, hasDynamicArgument: true, diagnostics); } } if (finalApplicableCandidates.Length != 1 && + Compilation.LanguageVersion > LanguageVersion.CSharp12 && // The following check (while correct) is redundant otherwise HasApplicableMemberWithPossiblyExpandedNonArrayParamsCollection(analyzedArguments.Arguments, finalApplicableCandidates)) { Error(diagnostics, @@ -9355,7 +9749,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( return BindDynamicIndexer(syntax, receiver, analyzedArguments, finalApplicableCandidates.SelectAsArray(r => r.Member), diagnostics); } - return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, overloadResolutionResult, diagnostics); + return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, overloadResolutionResult, hasDynamicArgument: false, diagnostics); } private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( @@ -9364,6 +9758,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( ArrayBuilder propertyGroup, AnalyzedArguments analyzedArguments, OverloadResolutionResult overloadResolutionResult, + bool hasDynamicArgument, BindingDiagnosticBag diagnostics) { BoundExpression propertyAccess; @@ -9425,8 +9820,24 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( MemberResolutionResult resolutionResult = overloadResolutionResult.ValidResult; PropertySymbol property = resolutionResult.Member; - var isExpanded = resolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; - var argsToParams = resolutionResult.Result.ArgsToParamsOpt; + bool forceDynamicResultType = false; + var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + + // Due to backward compatibility, invocations statically bound in presence of dynamic arguments + // should have dynamic result if their dynamic binding succeeded in C# 12 and there are no + // obvious reasons for the runtime binder to fail (ref return, for example). + if (hasDynamicArgument && + !(property.Type.IsDynamic() || property.ReturnsByRef || + !Conversions.ClassifyConversionFromExpressionType(property.Type, Compilation.DynamicType, isChecked: false, ref useSiteInfo).IsImplicit || + IsMemberWithExpandedNonArrayParamsCollection(resolutionResult))) + { + var tryDynamicAccessDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false); + BindDynamicIndexer(syntax, receiver, analyzedArguments, ImmutableArray.Create(property), tryDynamicAccessDiagnostics); + forceDynamicResultType = !tryDynamicAccessDiagnostics.HasAnyResolvedErrors(); + tryDynamicAccessDiagnostics.Free(); + } + + diagnostics.Add(syntax, useSiteInfo); ReportDiagnosticsIfObsolete(diagnostics, property, syntax, hasBaseReceiver: receiver != null && receiver.Kind == BoundKind.BaseReference); @@ -9435,7 +9846,8 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( receiver = ReplaceTypeOrValueReceiver(receiver, property.IsStatic, diagnostics); - this.CheckAndCoerceArguments(resolutionResult, analyzedArguments, diagnostics, receiver, invokedAsExtensionMethod: false); + ImmutableArray argsToParams; + this.CheckAndCoerceArguments(syntax, resolutionResult, analyzedArguments, diagnostics, receiver, invokedAsExtensionMethod: false, out argsToParams); if (!gotError && receiver != null && receiver.Kind == BoundKind.ThisReference && receiver.WasCompilerGenerated) { @@ -9454,10 +9866,10 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( arguments, argumentNames, argumentRefKinds, - isExpanded, + expanded: resolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm, argsToParams, defaultArguments: default, - property.Type, + forceDynamicResultType ? Compilation.DynamicType : property.Type, gotError); } @@ -9972,7 +10384,8 @@ private MethodGroupResolution ResolveDefaultMethodGroup( OverloadResolution.Options.IsFunctionPointerResolution | OverloadResolution.Options.InferWithDynamic | OverloadResolution.Options.IgnoreNormalFormIfHasValidParamsParameter | - OverloadResolution.Options.DynamicResolution)) == 0); + OverloadResolution.Options.DynamicResolution | + OverloadResolution.Options.DynamicConvertsToAnything)) == 0); var methods = node.Methods; if (methods.Length == 0) @@ -10366,8 +10779,7 @@ bool satisfiesConstraintChecks(MethodSymbol method) fieldsBuilder.Add(new AnonymousTypeField(name: "", location, returnType, returnRefKind, ScopedKind.None)); var typeDescr = new AnonymousTypeDescriptor(fieldsBuilder.ToImmutableAndFree(), location); - return Compilation.AnonymousTypeManager.ConstructAnonymousDelegateSymbol(typeDescr, - checkParamsCollectionsFeatureAvailability: hasParams && !parameters[^1].Type.IsSZArray() && Compilation.SourceModule != methodSymbol.ContainingModule); + return Compilation.AnonymousTypeManager.ConstructAnonymousDelegateSymbol(typeDescr); static bool checkConstraints(CSharpCompilation compilation, ConversionsBase conversions, NamedTypeSymbol delegateType, ImmutableArray typeArguments) { diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs index b8e9ecaf84cde..a29aa644b645d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs @@ -306,7 +306,11 @@ private static BoundFieldEqualsValue BindFieldInitializer(Binder binder, FieldSy } binder = new ExecutableCodeBinder(equalsValueClauseNode, fieldSymbol, new LocalScopeBinder(binder)); - BoundFieldEqualsValue boundInitValue = binder.BindFieldInitializer(fieldSymbol, equalsValueClauseNode, initializerDiagnostics); + BoundFieldEqualsValue boundInitValue = binder.BindWithLambdaBindingCountDiagnostics( + equalsValueClauseNode, + fieldSymbol, + initializerDiagnostics, + static (binder, equalsValueClauseNode, fieldSymbol, initializerDiagnostics) => binder.BindFieldInitializer(fieldSymbol, equalsValueClauseNode, initializerDiagnostics)); return boundInitValue; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs index c5a27ffff11b7..0a15fecb093c9 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs @@ -873,227 +873,5 @@ private ImmutableArray BindInterpolatedStringParts(BoundUnconve positionInfo.Free(); return (builderAppendCallsArray.ToImmutableAndFree(), builderPatternExpectsBool ?? false, positionInfoArray.ToImmutableAndFree(), baseStringLength, numFormatHoles); } - - private BoundExpression BindInterpolatedStringHandlerInMemberCall( - BoundExpression unconvertedString, - TypeSymbol handlerType, - ArrayBuilder arguments, - ImmutableArray parameters, - ref MemberAnalysisResult memberAnalysisResult, - int interpolatedStringArgNum, - BoundExpression? receiver, - BindingDiagnosticBag diagnostics) - { - Debug.Assert(unconvertedString is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true }); - var interpolatedStringConversion = memberAnalysisResult.ConversionForArg(interpolatedStringArgNum); - Debug.Assert(interpolatedStringConversion.IsInterpolatedStringHandler); - Debug.Assert(handlerType is NamedTypeSymbol { IsInterpolatedStringHandlerType: true }); - - var correspondingParameter = GetCorrespondingParameter(ref memberAnalysisResult, parameters, interpolatedStringArgNum); - var handlerParameterIndexes = correspondingParameter.InterpolatedStringHandlerArgumentIndexes; - - if (memberAnalysisResult.Kind == MemberResolutionKind.ApplicableInExpandedForm && correspondingParameter.Ordinal == parameters.Length - 1) - { - Debug.Assert(handlerParameterIndexes.IsEmpty); - - // No arguments, fall back to the standard conversion steps. - return CreateConversion( - unconvertedString.Syntax, - unconvertedString, - interpolatedStringConversion, - isCast: false, - conversionGroupOpt: null, - handlerType, - diagnostics); - } - - if (correspondingParameter.HasInterpolatedStringHandlerArgumentError) - { - // The InterpolatedStringHandlerArgumentAttribute applied to parameter '{0}' is malformed and cannot be interpreted. Construct an instance of '{1}' manually. - diagnostics.Add(ErrorCode.ERR_InterpolatedStringHandlerArgumentAttributeMalformed, unconvertedString.Syntax.Location, correspondingParameter, handlerType); - return CreateConversion( - unconvertedString.Syntax, - unconvertedString, - interpolatedStringConversion, - isCast: false, - conversionGroupOpt: null, - wasCompilerGenerated: false, - handlerType, - diagnostics, - hasErrors: true); - } - - if (handlerParameterIndexes.IsEmpty) - { - // No arguments, fall back to the standard conversion steps. - return CreateConversion( - unconvertedString.Syntax, - unconvertedString, - interpolatedStringConversion, - isCast: false, - conversionGroupOpt: null, - handlerType, - diagnostics); - } - - Debug.Assert(handlerParameterIndexes.All((index, paramLength) => index >= BoundInterpolatedStringArgumentPlaceholder.InstanceParameter && index < paramLength, - parameters.Length)); - - // We need to find the appropriate argument expression for every expected parameter, and error on any that occur after the current parameter - - ImmutableArray handlerArgumentIndexes; - - if (memberAnalysisResult.ArgsToParamsOpt.IsDefault && arguments.Count == parameters.Length) - { - // No parameters are missing and no remapped indexes, we can just use the original indexes - handlerArgumentIndexes = handlerParameterIndexes; - } - else - { - // Args and parameters were reordered via named parameters, or parameters are missing. Find the correct argument index for each parameter. - var handlerArgumentIndexesBuilder = ArrayBuilder.GetInstance(handlerParameterIndexes.Length, fillWithValue: BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter); - for (int handlerParameterIndex = 0; handlerParameterIndex < handlerParameterIndexes.Length; handlerParameterIndex++) - { - int handlerParameter = handlerParameterIndexes[handlerParameterIndex]; - Debug.Assert(handlerArgumentIndexesBuilder[handlerParameterIndex] is BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter); - - if (handlerParameter == BoundInterpolatedStringArgumentPlaceholder.InstanceParameter) - { - handlerArgumentIndexesBuilder[handlerParameterIndex] = handlerParameter; - continue; - } - - for (int argumentIndex = 0; argumentIndex < arguments.Count; argumentIndex++) - { - // The index in the original parameter list we're looking to match up. - int argumentParameterIndex = memberAnalysisResult.ParameterFromArgument(argumentIndex); - // Is the original parameter index of the current argument the parameter index that was specified in the attribute? - if (argumentParameterIndex == handlerParameter) - { - // We can't just bail out on the first match: users can duplicate parameters in attributes, causing the same value to be passed twice. - handlerArgumentIndexesBuilder[handlerParameterIndex] = argumentIndex; - } - } - } - - handlerArgumentIndexes = handlerArgumentIndexesBuilder.ToImmutableAndFree(); - } - - var argumentPlaceholdersBuilder = ArrayBuilder.GetInstance(handlerArgumentIndexes.Length); - var argumentRefKindsBuilder = ArrayBuilder.GetInstance(handlerArgumentIndexes.Length); - bool hasErrors = false; - - // Now, go through all the specified arguments and see if any were specified _after_ the interpolated string, and construct - // a set of placeholders for overload resolution. - for (int i = 0; i < handlerArgumentIndexes.Length; i++) - { - int argumentIndex = handlerArgumentIndexes[i]; - Debug.Assert(argumentIndex != interpolatedStringArgNum); - - RefKind refKind; - TypeSymbol placeholderType; - switch (argumentIndex) - { - case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter: - Debug.Assert(receiver!.Type is not null); - refKind = RefKind.None; - placeholderType = receiver.Type; - break; - case BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter: - { - // Don't error if the parameter isn't optional or params: the user will already have an error for missing an optional parameter or overload resolution failed. - // If it is optional, then they could otherwise not specify the parameter and that's an error - var originalParameterIndex = handlerParameterIndexes[i]; - var parameter = parameters[originalParameterIndex]; - if (parameter.IsOptional || - (memberAnalysisResult.Kind == MemberResolutionKind.ApplicableInExpandedForm && originalParameterIndex + 1 == parameters.Length)) - { - // Parameter '{0}' is not explicitly provided, but is used as an argument to the interpolated string handler conversion on parameter '{1}'. Specify the value of '{0}' before '{1}'. - diagnostics.Add( - ErrorCode.ERR_InterpolatedStringHandlerArgumentOptionalNotSpecified, - unconvertedString.Syntax.Location, - parameter.Name, - correspondingParameter.Name); - hasErrors = true; - } - - refKind = parameter.RefKind; - placeholderType = parameter.Type; - } - break; - default: - { - var originalParameterIndex = handlerParameterIndexes[i]; - var parameter = parameters[originalParameterIndex]; - if (argumentIndex > interpolatedStringArgNum) - { - // Parameter '{0}' is an argument to the interpolated string handler conversion on parameter '{1}', but the corresponding argument is specified after the interpolated string expression. Reorder the arguments to move '{0}' before '{1}'. - diagnostics.Add( - ErrorCode.ERR_InterpolatedStringHandlerArgumentLocatedAfterInterpolatedString, - arguments[argumentIndex].Syntax.Location, - parameter.Name, - correspondingParameter.Name); - hasErrors = true; - } - - refKind = parameter.RefKind; - placeholderType = parameter.Type; - } - break; - } - - SyntaxNode placeholderSyntax; - bool isSuppressed; - - switch (argumentIndex) - { - case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter: - Debug.Assert(receiver != null); - isSuppressed = receiver.IsSuppressed; - placeholderSyntax = receiver.Syntax; - break; - case BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter: - placeholderSyntax = unconvertedString.Syntax; - isSuppressed = false; - break; - case >= 0: - placeholderSyntax = arguments[argumentIndex].Syntax; - isSuppressed = arguments[argumentIndex].IsSuppressed; - break; - default: - throw ExceptionUtilities.UnexpectedValue(argumentIndex); - } - - argumentPlaceholdersBuilder.Add( - (BoundInterpolatedStringArgumentPlaceholder)(new BoundInterpolatedStringArgumentPlaceholder( - placeholderSyntax, - argumentIndex, - placeholderType, - hasErrors: argumentIndex == BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter) - { WasCompilerGenerated = true }.WithSuppression(isSuppressed))); - // We use the parameter refkind, rather than what the argument was actually passed with, because that will suppress duplicated errors - // about arguments being passed with the wrong RefKind. The user will have already gotten an error about mismatched RefKinds or it will - // be a place where refkinds are allowed to differ - argumentRefKindsBuilder.Add(refKind == RefKind.RefReadOnlyParameter ? RefKind.In : refKind); - } - - var interpolatedString = BindUnconvertedInterpolatedExpressionToHandlerType( - unconvertedString, - (NamedTypeSymbol)handlerType, - diagnostics, - additionalConstructorArguments: argumentPlaceholdersBuilder.ToImmutableAndFree(), - additionalConstructorRefKinds: argumentRefKindsBuilder.ToImmutableAndFree()); - - return new BoundConversion( - interpolatedString.Syntax, - interpolatedString, - interpolatedStringConversion, - @checked: CheckOverflowAtRuntime, - explicitCastInCode: false, - conversionGroupOpt: null, - constantValueOpt: null, - handlerType, - hasErrors || interpolatedString.HasErrors); - } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 366a0e05659ac..839d23ee94800 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -380,6 +380,7 @@ private BoundExpression BindInvocationExpression( return result; } +#nullable enable private BoundExpression BindDynamicInvocation( SyntaxNode node, BoundExpression expression, @@ -388,21 +389,14 @@ private BoundExpression BindDynamicInvocation( BindingDiagnosticBag diagnostics, CSharpSyntaxNode queryClause) { - // - // !!! ATTENTION !!! - // - // In terms of errors relevant for HasCollectionExpressionApplicableAddMethod check - // this function should be kept in sync with local function - // HasCollectionExpressionApplicableAddMethod.bindDynamicInvocation - // - CheckNamedArgumentsForDynamicInvocation(arguments, diagnostics); bool hasErrors = false; + BoundExpression? receiver; if (expression.Kind == BoundKind.MethodGroup) { BoundMethodGroup methodGroup = (BoundMethodGroup)expression; - BoundExpression receiver = methodGroup.ReceiverOpt; + receiver = methodGroup.ReceiverOpt; // receiver is null if we are calling a static method declared on an outer class via its simple name: if (receiver != null) @@ -422,6 +416,7 @@ private BoundExpression BindDynamicInvocation( // the runtime binder would ignore the receiver, but in a ctor initializer we can't read "this" before // the base constructor is called. We need to handle this as a type qualified static method call. // Also applicable to things like field initializers, which run before the ctor initializer. + Debug.Assert(ContainingType is not null); expression = methodGroup.Update( methodGroup.TypeArgumentsOpt, methodGroup.Name, @@ -464,12 +459,21 @@ private BoundExpression BindDynamicInvocation( else { expression = BindToNaturalType(expression, diagnostics); + + if (expression is BoundDynamicMemberAccess memberAccess) + { + receiver = memberAccess.Receiver; + } + else + { + receiver = expression; + } } ImmutableArray argArray = BuildArgumentsForDynamicInvocation(arguments, diagnostics); var refKindsArray = arguments.RefKinds.ToImmutableOrNull(); - hasErrors &= ReportBadDynamicArguments(node, argArray, refKindsArray, diagnostics, queryClause); + hasErrors &= ReportBadDynamicArguments(node, receiver, argArray, refKindsArray, diagnostics, queryClause); return new BoundDynamicInvocation( node, @@ -481,6 +485,7 @@ private BoundExpression BindDynamicInvocation( type: Compilation.DynamicType, hasErrors: hasErrors); } +#nullable disable private void CheckNamedArgumentsForDynamicInvocation(AnalyzedArguments arguments, BindingDiagnosticBag diagnostics) { @@ -527,16 +532,26 @@ private ImmutableArray BuildArgumentsForDynamicInvocation(Analy } // Returns true if there were errors. +#nullable enable private static bool ReportBadDynamicArguments( SyntaxNode node, + BoundExpression? receiver, ImmutableArray arguments, ImmutableArray refKinds, BindingDiagnosticBag diagnostics, - CSharpSyntaxNode queryClause) + CSharpSyntaxNode? queryClause) { bool hasErrors = false; bool reportedBadQuery = false; + if (receiver != null && !IsLegalDynamicOperand(receiver)) + { + // Cannot perform a dynamic invocation on an expression with type '{0}'. + Debug.Assert(receiver.Type is not null); + Error(diagnostics, ErrorCode.ERR_CannotDynamicInvokeOnExpression, receiver.Syntax, receiver.Type); + hasErrors = true; + } + if (!refKinds.IsDefault) { for (int argIndex = 0; argIndex < refKinds.Length; argIndex++) @@ -584,7 +599,7 @@ private static bool ReportBadDynamicArguments( { // Lambdas,anonymous methods and method groups are the typeless expressions that // are not usable as dynamic arguments; if we get here then the expression must have a type. - Debug.Assert((object)arg.Type != null); + Debug.Assert((object?)arg.Type != null); // error CS1978: Cannot use an expression of type 'int*' as an argument to a dynamically dispatched operation Error(diagnostics, ErrorCode.ERR_BadDynamicMethodArg, arg.Syntax, arg.Type); @@ -594,6 +609,7 @@ private static bool ReportBadDynamicArguments( } return hasErrors; } +#nullable disable private BoundExpression BindDelegateInvocation( SyntaxNode node, @@ -646,14 +662,19 @@ private BoundExpression BindDelegateInvocation( result = BindDynamicInvocation(node, boundExpression, analyzedArguments, overloadResolutionResult.GetAllApplicableMembers(), diagnostics, queryClause); } + // For C# 12 and earlier always bind at runtime. + else if (Compilation.LanguageVersion > LanguageVersion.CSharp12) + { + result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, hasDynamicArgument: true, boundExpression, diagnostics, queryClause); + } else { - result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, queryClause); + result = BindDynamicInvocation(node, boundExpression, analyzedArguments, overloadResolutionResult.GetAllApplicableMembers(), diagnostics, queryClause); } } else { - result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, queryClause); + result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, hasDynamicArgument: false, boundExpression, diagnostics, queryClause); } overloadResolutionResult.Free(); @@ -690,6 +711,13 @@ private bool HasApplicableMemberWithPossiblyExpandedNonArrayParamsCollection(MemberResolutionResult candidate) + where TMember : Symbol + { + return candidate.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm && + !candidate.Member.GetParameters().Last().Type.IsSZArray(); + } + private BoundExpression BindMethodGroupInvocation( SyntaxNode syntax, SyntaxNode expression, @@ -764,7 +792,7 @@ private BoundExpression BindMethodGroupInvocation( // we want to force any unbound lambda arguments to cache an appropriate conversion if possible; see 9448. result = BindInvocationExpressionContinued( syntax, expression, methodName, resolution.OverloadResolutionResult, resolution.AnalyzedArguments, - resolution.MethodGroup, delegateTypeOpt: null, diagnostics: BindingDiagnosticBag.Discarded, queryClause: queryClause); + resolution.MethodGroup, delegateTypeOpt: null, hasDynamicArgument: false, methodGroup, diagnostics: BindingDiagnosticBag.Discarded, queryClause: queryClause); } // Since the resolution is non-empty and has no diagnostics, the LookupResultKind in its MethodGroup is uninteresting. @@ -828,7 +856,7 @@ private BoundExpression BindMethodGroupInvocation( { result = BindInvocationExpressionContinued( syntax, expression, methodName, resolution.OverloadResolutionResult, resolution.AnalyzedArguments, - resolution.MethodGroup, delegateTypeOpt: null, diagnostics: diagnostics, queryClause: queryClause); + resolution.MethodGroup, delegateTypeOpt: null, hasDynamicArgument: false, methodGroup, diagnostics: diagnostics, queryClause: queryClause); } } } @@ -850,6 +878,7 @@ private void ReportDynamicInvocationWarnings(SyntaxNode syntax, BoundMethodGroup } if (finalApplicableCandidates.Length != 1 && + Compilation.LanguageVersion > LanguageVersion.CSharp12 && // The following check (while correct) is redundant otherwise HasApplicableMemberWithPossiblyExpandedNonArrayParamsCollection(resolution.AnalyzedArguments.Arguments, finalApplicableCandidates)) { Error(diagnostics, @@ -891,6 +920,14 @@ private bool CanEarlyBindSingleCandidateInvocationWithDynamicArgument( MemberResolutionResult methodResolutionResult, MethodSymbol singleCandidate) { + // + // !!! ATTENTION !!! + // + // In terms of errors relevant for HasCollectionExpressionApplicableAddMethod check + // this function should be kept in sync with local function + // HasCollectionExpressionApplicableAddMethod.canEarlyBindSingleCandidateInvocationWithDynamicArgument + // + if (boundMethodGroup.TypeArgumentsOpt.IsDefaultOrEmpty && singleCandidate.IsGenericMethod) { // If we call an unconstructed generic function with a @@ -975,23 +1012,32 @@ private BoundExpression TryEarlyBindSingleCandidateInvocationWithDynamicArgument return null; } - var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); - resultWithSingleCandidate.ResultsBuilder.Add(methodResolutionResult); + // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for local functions. + if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || + singleCandidate.MethodKind == MethodKind.LocalFunction) + { + var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); + resultWithSingleCandidate.ResultsBuilder.Add(methodResolutionResult); - BoundExpression result = BindInvocationExpressionContinued( - node: syntax, - expression: expression, - methodName: methodName, - result: resultWithSingleCandidate, - analyzedArguments: resolution.AnalyzedArguments, - methodGroup: resolution.MethodGroup, - delegateTypeOpt: null, - diagnostics: diagnostics, - queryClause: queryClause); + BoundExpression result = BindInvocationExpressionContinued( + node: syntax, + expression: expression, + methodName: methodName, + result: resultWithSingleCandidate, + analyzedArguments: resolution.AnalyzedArguments, + methodGroup: resolution.MethodGroup, + delegateTypeOpt: null, + hasDynamicArgument: true, + boundMethodGroup, + diagnostics: diagnostics, + queryClause: queryClause); - resultWithSingleCandidate.Free(); + resultWithSingleCandidate.Free(); - return result; + return result; + } + + return null; } private ImmutableArray> GetCandidatesPassingFinalValidation( @@ -1130,6 +1176,8 @@ private BoundCall BindInvocationExpressionContinued( AnalyzedArguments analyzedArguments, MethodGroup methodGroup, NamedTypeSymbol delegateTypeOpt, + bool hasDynamicArgument, + BoundExpression targetMethodGroupOrDelegateInstance, BindingDiagnosticBag diagnostics, CSharpSyntaxNode queryClause = null) { @@ -1205,12 +1253,32 @@ private BoundCall BindInvocationExpressionContinued( GetOriginalMethods(result), methodGroup.ResultKind, methodGroup.TypeArguments.ToImmutable(), analyzedArguments, invokedAsExtensionMethod: invokedAsExtensionMethod, isDelegate: ((object)delegateTypeOpt != null)); } - // Otherwise, there were no dynamic arguments and overload resolution found a unique best candidate. + // Otherwise, overload resolution found a unique best candidate. // We still have to determine if it passes final validation. var methodResult = result.ValidResult; var returnType = methodResult.Member.ReturnType; var method = methodResult.Member; + bool forceDynamicResultType = false; + + var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + + // Due to backward compatibility, invocations statically bound in presence of dynamic arguments + // should have dynamic result if their dynamic binding succeeded in C# 12 and there are no + // obvious reasons for the runtime binder to fail (ref return, for example). + if (hasDynamicArgument && + !(methodGroup.IsExtensionMethodGroup || method.MethodKind == MethodKind.LocalFunction || + method.ReturnsVoid || method.ReturnsByRef || returnType.IsDynamic() || + !Conversions.ClassifyConversionFromExpressionType(returnType, Compilation.DynamicType, isChecked: false, ref useSiteInfo).IsImplicit || + IsMemberWithExpandedNonArrayParamsCollection(methodResult))) + { + var tryDynamicInvocationDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false); + BindDynamicInvocation(node, targetMethodGroupOrDelegateInstance, analyzedArguments, ImmutableArray.Create(method), tryDynamicInvocationDiagnostics, queryClause); + forceDynamicResultType = !tryDynamicInvocationDiagnostics.HasAnyResolvedErrors(); + tryDynamicInvocationDiagnostics.Free(); + } + + diagnostics.Add(node, useSiteInfo); // It is possible that overload resolution succeeded, but we have chosen an // instance method and we're in a static method. A careful reading of the @@ -1221,12 +1289,12 @@ private BoundCall BindInvocationExpressionContinued( var receiver = ReplaceTypeOrValueReceiver(methodGroup.Receiver, !method.RequiresInstanceReceiver && !invokedAsExtensionMethod, diagnostics); - this.CheckAndCoerceArguments(methodResult, analyzedArguments, diagnostics, receiver, invokedAsExtensionMethod: invokedAsExtensionMethod); + ImmutableArray argsToParams; + this.CheckAndCoerceArguments(node, methodResult, analyzedArguments, diagnostics, receiver, invokedAsExtensionMethod: invokedAsExtensionMethod, out argsToParams); var expanded = methodResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; - var argsToParams = methodResult.Result.ArgsToParamsOpt; - BindDefaultArgumentsAndParamsCollection(node, method.Parameters, analyzedArguments.Arguments, analyzedArguments.RefKinds, analyzedArguments.Names, ref argsToParams, out var defaultArguments, expanded, enableCallerInfo: true, diagnostics); + BindDefaultArguments(node, method.Parameters, analyzedArguments.Arguments, analyzedArguments.RefKinds, analyzedArguments.Names, ref argsToParams, out var defaultArguments, expanded, enableCallerInfo: true, diagnostics); // Note: we specifically want to do final validation (7.6.5.1) without checking delegate compatibility (15.2), // so we're calling MethodGroupFinalValidation directly, rather than via MethodGroupConversionHasErrors. @@ -1340,7 +1408,9 @@ private BoundCall BindInvocationExpressionContinued( return new BoundCall(node, receiver, initialBindingReceiverIsSubjectToCloning: ReceiverIsSubjectToCloning(receiver, method), method, args, argNames, argRefKinds, isDelegateCall: isDelegateCall, expanded: expanded, invokedAsExtensionMethod: invokedAsExtensionMethod, - argsToParamsOpt: argsToParams, defaultArguments, resultKind: LookupResultKind.Viable, type: returnType, hasErrors: gotError); + argsToParamsOpt: argsToParams, defaultArguments, resultKind: LookupResultKind.Viable, + type: forceDynamicResultType ? Compilation.DynamicType : returnType, + hasErrors: gotError); } #nullable enable @@ -1487,7 +1557,7 @@ private BoundExpression GetDefaultParameterSpecialNoConversion(SyntaxNode syntax return parameter; } - internal void BindDefaultArgumentsAndParamsCollection( + internal void BindDefaultArguments( SyntaxNode node, ImmutableArray parameters, ArrayBuilder argumentsBuilder, @@ -1500,12 +1570,7 @@ internal void BindDefaultArgumentsAndParamsCollection( BindingDiagnosticBag diagnostics, Symbol? attributedMember = null) { - int firstParamsArgument = -1; int paramsIndex = parameters.Length - 1; - var paramsArgsBuilder = expanded ? ArrayBuilder.GetInstance() : null; - - Debug.Assert(!argumentsBuilder.Any(a => a.IsParamsArrayOrCollection)); - var visitedParameters = BitVector.Create(parameters.Length); for (var i = 0; i < argumentsBuilder.Count; i++) { @@ -1516,22 +1581,10 @@ internal void BindDefaultArgumentsAndParamsCollection( if (expanded && parameter.Ordinal == paramsIndex) { - Debug.Assert(paramsArgsBuilder is not null); - Debug.Assert(paramsArgsBuilder.Count == 0); - - firstParamsArgument = i; - paramsArgsBuilder.Add(argumentsBuilder[i]); - - for (int remainingArgument = i + 1; remainingArgument < argumentsBuilder.Count; ++remainingArgument) - { - if (GetCorrespondingParameter(remainingArgument, parameters, argsToParamsOpt, expanded: true)?.Ordinal != paramsIndex) - { - break; - } - - paramsArgsBuilder.Add(argumentsBuilder[remainingArgument]); - i++; - } + expanded = false; // For the reminder of the method treat this as non-expanded case + Debug.Assert(argumentsBuilder[i].IsParamsArrayOrCollection); + Debug.Assert(i + 1 == argumentsBuilder.Count || + GetCorrespondingParameter(i + 1, parameters, argsToParamsOpt, expanded: true)?.Ordinal != paramsIndex); } } } @@ -1550,7 +1603,6 @@ internal void BindDefaultArgumentsAndParamsCollection( Debug.Assert(argumentRefKindsBuilder is null || argumentRefKindsBuilder.Count == 0 || argumentRefKindsBuilder.Count == argumentsBuilder.Count); Debug.Assert(namesBuilder is null || namesBuilder.Count == 0 || namesBuilder.Count == argumentsBuilder.Count); Debug.Assert(argsToParamsOpt.IsDefault || argsToParamsOpt.Length == argumentsBuilder.Count); - Debug.Assert(paramsArgsBuilder is null); defaultArguments = default; return; } @@ -1562,146 +1614,6 @@ internal void BindDefaultArgumentsAndParamsCollection( argsToParamsBuilder.AddRange(argsToParamsOpt); } - BoundExpression? collection = null; - - if (expanded) - { - Debug.Assert(paramsArgsBuilder is not null); - ImmutableArray collectionArgs = paramsArgsBuilder.ToImmutableAndFree(); - int collectionArgsLength = collectionArgs.Length; - - TypeSymbol collectionType = parameters[paramsIndex].Type; - - if (collectionType is ArrayTypeSymbol { IsSZArray: true }) - { - TypeSymbol int32Type = GetSpecialType(SpecialType.System_Int32, diagnostics, node); - BoundExpression arraySize = new BoundLiteral(node, ConstantValue.Create(collectionArgsLength), int32Type) { WasCompilerGenerated = true }; - - collection = new BoundArrayCreation( - node, - ImmutableArray.Create(arraySize), - new BoundArrayInitialization(node, isInferred: false, collectionArgs) { WasCompilerGenerated = true }, - collectionType) - { WasCompilerGenerated = true, IsParamsArrayOrCollection = true }; - } - else - { - if (Compilation.SourceModule != parameters[paramsIndex].ContainingModule) - { - MessageID.IDS_FeatureParamsCollections.CheckFeatureAvailability(diagnostics, node); - } - - var unconvertedCollection = new BoundUnconvertedCollectionExpression(node, ImmutableArray.CastUp(collectionArgs)) { WasCompilerGenerated = true, IsParamsArrayOrCollection = true }; - CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); - Conversion conversion = Conversions.ClassifyImplicitConversionFromExpression(unconvertedCollection, collectionType, ref useSiteInfo); - diagnostics.Add(node, useSiteInfo); - - BoundCollectionExpression converted; - if (!conversion.Exists) - { - Debug.Assert(false); // Add test if this code path is reachable - GenerateImplicitConversionErrorForCollectionExpression(unconvertedCollection, collectionType, diagnostics); - converted = BindCollectionExpressionForErrorRecovery(unconvertedCollection, collectionType, inConversion: true, diagnostics); - } - else - { - Debug.Assert(conversion.IsCollectionExpression); - - bool infiniteRecursion = false; - if (conversion.GetCollectionExpressionTypeKind(out _, out MethodSymbol? constructor, out bool isExpanded) == CollectionExpressionTypeKind.ImplementsIEnumerable && - isExpanded) - { - Debug.Assert(constructor is not null); - - // Check for infinite recursion through the constructor - var constructorSet = PooledHashSet.GetInstance(); - constructorSet.Add(constructor.OriginalDefinition); - - BoundUnconvertedCollectionExpression? emptyCollection = null; - - while (true) - { - var paramsType = constructor.Parameters[^1].Type; - if (!paramsType.IsSZArray()) - { - var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; - emptyCollection ??= new BoundUnconvertedCollectionExpression(node, ImmutableArray.CastUp(ImmutableArray.Empty)) { WasCompilerGenerated = true, IsParamsArrayOrCollection = true }; - Conversion nextConversion = Conversions.ClassifyImplicitConversionFromExpression(emptyCollection, paramsType, ref discardedUseSiteInfo); - - if (nextConversion.Exists && - nextConversion.GetCollectionExpressionTypeKind(out _, out constructor, out isExpanded) == CollectionExpressionTypeKind.ImplementsIEnumerable && - isExpanded) - { - Debug.Assert(constructor is not null); - - if (constructorSet.Add(constructor.OriginalDefinition)) - { - continue; - } - - infiniteRecursion = true; - } - } - - break; - } - - constructorSet.Free(); - } - - if (infiniteRecursion) - { - Debug.Assert(constructor is not null); - Error(diagnostics, ErrorCode.ERR_ParamsCollectionInfiniteChainOfConstructorCalls, node, collectionType, constructor.OriginalDefinition); - converted = BindCollectionExpressionForErrorRecovery(unconvertedCollection, collectionType, inConversion: true, diagnostics); - } - else - { - converted = ConvertCollectionExpression(unconvertedCollection, collectionType, conversion, diagnostics); - } - } - - collection = new BoundConversion( - node, - converted, - conversion, - @checked: CheckOverflowAtRuntime, - explicitCastInCode: false, - conversionGroupOpt: null, - constantValueOpt: null, - type: collectionType) - { WasCompilerGenerated = true, IsParamsArrayOrCollection = true }; - } - - Debug.Assert(collection.IsParamsArrayOrCollection); - - if (collectionArgsLength != 0) - { - Debug.Assert(firstParamsArgument != -1); - Debug.Assert(!haveDefaultArguments || collectionArgsLength == 1); - Debug.Assert(collectionArgsLength == 1 || firstParamsArgument + collectionArgsLength == argumentsBuilder.Count); - - for (var i = firstParamsArgument + collectionArgsLength - 1; i != firstParamsArgument; i--) - { - argumentsBuilder.RemoveAt(i); - argsToParamsBuilder?.RemoveAt(i); - - if (argumentRefKindsBuilder is { Count: > 0 }) - { - argumentRefKindsBuilder.RemoveAt(i); - } - - if (namesBuilder is { Count: > 0 }) - { - namesBuilder.RemoveAt(i); - } - } - - argumentsBuilder[firstParamsArgument] = collection; - collection = null; - } - } - // only proceed with binding default arguments if we know there is some parameter that has not been matched by an explicit argument if (haveDefaultArguments) { @@ -1750,11 +1662,10 @@ internal void BindDefaultArgumentsAndParamsCollection( defaultArguments = default; } - if (collection is not null) + if (expanded) { - Debug.Assert(expanded); - Debug.Assert(firstParamsArgument == -1); - + // Create an empty collection + BoundExpression collection = CreateParamsCollection(node, parameters[paramsIndex], collectionArgs: ImmutableArray.Empty, diagnostics); argumentsBuilder.Add(collection); argsToParamsBuilder?.Add(paramsIndex); @@ -1900,6 +1811,116 @@ static int getArgumentIndex(int parameterIndex, ImmutableArray argsToParams } + private BoundExpression CreateParamsCollection(SyntaxNode node, ParameterSymbol paramsParameter, ImmutableArray collectionArgs, BindingDiagnosticBag diagnostics) + { + TypeSymbol collectionType = paramsParameter.Type; + BoundExpression collection; + + if (collectionType is ArrayTypeSymbol { IsSZArray: true }) + { + TypeSymbol int32Type = GetSpecialType(SpecialType.System_Int32, diagnostics, node); + BoundExpression arraySize = new BoundLiteral(node, ConstantValue.Create(collectionArgs.Length), int32Type) { WasCompilerGenerated = true }; + + collection = new BoundArrayCreation( + node, + ImmutableArray.Create(arraySize), + new BoundArrayInitialization(node, isInferred: false, collectionArgs) { WasCompilerGenerated = true }, + collectionType) + { WasCompilerGenerated = true, IsParamsArrayOrCollection = true }; + } + else + { + if (Compilation.SourceModule != paramsParameter.ContainingModule) + { + MessageID.IDS_FeatureParamsCollections.CheckFeatureAvailability(diagnostics, node); + } + + var unconvertedCollection = new BoundUnconvertedCollectionExpression(node, ImmutableArray.CastUp(collectionArgs)) { WasCompilerGenerated = true, IsParamsArrayOrCollection = true }; + CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + Conversion conversion = Conversions.ClassifyImplicitConversionFromExpression(unconvertedCollection, collectionType, ref useSiteInfo); + diagnostics.Add(node, useSiteInfo); + + BoundCollectionExpression converted; + if (!conversion.Exists) + { + Debug.Assert(false); // Add test if this code path is reachable + GenerateImplicitConversionErrorForCollectionExpression(unconvertedCollection, collectionType, diagnostics); + converted = BindCollectionExpressionForErrorRecovery(unconvertedCollection, collectionType, inConversion: true, diagnostics); + } + else + { + Debug.Assert(conversion.IsCollectionExpression); + + bool infiniteRecursion = false; + if (conversion.GetCollectionExpressionTypeKind(out _, out MethodSymbol? constructor, out bool isExpanded) == CollectionExpressionTypeKind.ImplementsIEnumerable && + isExpanded) + { + Debug.Assert(constructor is not null); + + // Check for infinite recursion through the constructor + var constructorSet = PooledHashSet.GetInstance(); + constructorSet.Add(constructor.OriginalDefinition); + + BoundUnconvertedCollectionExpression? emptyCollection = null; + + while (true) + { + var paramsType = constructor.Parameters[^1].Type; + if (!paramsType.IsSZArray()) + { + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + emptyCollection ??= new BoundUnconvertedCollectionExpression(node, ImmutableArray.CastUp(ImmutableArray.Empty)) { WasCompilerGenerated = true, IsParamsArrayOrCollection = true }; + Conversion nextConversion = Conversions.ClassifyImplicitConversionFromExpression(emptyCollection, paramsType, ref discardedUseSiteInfo); + + if (nextConversion.Exists && + nextConversion.GetCollectionExpressionTypeKind(out _, out constructor, out isExpanded) == CollectionExpressionTypeKind.ImplementsIEnumerable && + isExpanded) + { + Debug.Assert(constructor is not null); + + if (constructorSet.Add(constructor.OriginalDefinition)) + { + continue; + } + + infiniteRecursion = true; + } + } + + break; + } + + constructorSet.Free(); + } + + if (infiniteRecursion) + { + Debug.Assert(constructor is not null); + Error(diagnostics, ErrorCode.ERR_ParamsCollectionInfiniteChainOfConstructorCalls, node, collectionType, constructor.OriginalDefinition); + converted = BindCollectionExpressionForErrorRecovery(unconvertedCollection, collectionType, inConversion: true, diagnostics); + } + else + { + converted = ConvertCollectionExpression(unconvertedCollection, collectionType, conversion, diagnostics); + } + } + + collection = new BoundConversion( + node, + converted, + conversion, + @checked: CheckOverflowAtRuntime, + explicitCastInCode: false, + conversionGroupOpt: null, + constantValueOpt: null, + type: collectionType) + { WasCompilerGenerated = true, IsParamsArrayOrCollection = true }; + } + + Debug.Assert(collection.IsParamsArrayOrCollection); + return collection; + } + #nullable disable /// @@ -2516,7 +2537,7 @@ private BoundFunctionPointerInvocation BindFunctionPointerInvocation(SyntaxNode methodsBuilder.Free(); MemberResolutionResult methodResult = overloadResolutionResult.ValidResult; - CheckAndCoerceArguments(methodResult, analyzedArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false); + CheckAndCoerceArguments(node, methodResult, analyzedArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false, argsToParamsOpt: out _); var args = analyzedArguments.Arguments.ToImmutable(); var refKinds = analyzedArguments.RefKinds.ToImmutableOrNull(); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs index 9528274cc497e..498267c0fbd56 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.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 Microsoft.CodeAnalysis.CSharp.Symbols; @@ -422,5 +423,65 @@ private UnboundLambda BindAnonymousFunction(AnonymousFunctionExpressionSyntax sy return lambda; } + + // Please don't use thread local storage widely. This should be one of only a few uses. + [ThreadStatic] private static PooledDictionary? s_lambdaBindings; + + internal TResult BindWithLambdaBindingCountDiagnostics( + TSyntax syntax, + TArg arg, + BindingDiagnosticBag diagnostics, + Func bind) + where TSyntax : SyntaxNode + where TResult : BoundNode + { + Debug.Assert(s_lambdaBindings is null); + var bindings = PooledDictionary.GetInstance(); + s_lambdaBindings = bindings; + + try + { + TResult result = bind(this, syntax, arg, diagnostics); + + foreach (var pair in bindings) + { + // The particular max value is arbitrary, but large enough so diagnostics should + // only be reported for lambda expressions used as arguments to method calls + // where the product of the number of applicable overloads for that method call + // and for overloads for any containing lambda expressions is large. + const int maxLambdaBinding = 100; + int count = pair.Value; + if (count > maxLambdaBinding) + { + int truncatedToHundreds = (count / 100) * 100; + diagnostics.Add(ErrorCode.INF_TooManyBoundLambdas, GetAnonymousFunctionLocation(pair.Key), truncatedToHundreds); + } + } + + return result; + } + finally + { + bindings.Free(); + s_lambdaBindings = null; + } + } + + internal static void RecordLambdaBinding(SyntaxNode syntax) + { + var bindings = s_lambdaBindings; + if (bindings is null) + { + return; + } + if (bindings.TryGetValue(syntax, out int count)) + { + bindings[syntax] = ++count; + } + else + { + bindings.Add(syntax, 1); + } + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 40acd645365d9..6a69fe918bbfd 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -22,7 +22,7 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, { node.Left.CheckDeconstructionCompatibleArgument(diagnostics); - BoundExpression left = BindValue(node.Left, diagnostics, GetBinaryAssignmentKind(node.Kind())); + BoundExpression left = BindValue(node.Left, diagnostics, GetBinaryAssignmentKind(node.Kind()), dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(left, diagnostics); BoundExpression right = BindValue(node.Right, diagnostics, BindValueKind.RValue); BinaryOperatorKind kind = SyntaxKindToBinaryOperatorKind(node.Kind()); @@ -43,6 +43,8 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, } } + left = AdjustAssignmentTargetForDynamic(left, out bool forceDynamicResult); + if (left.HasAnyErrors || right.HasAnyErrors) { // NOTE: no overload resolution candidates. @@ -81,7 +83,7 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, finalPlaceholder: placeholder, finalConversion: conversion, LookupResultKind.Viable, - left.Type, + AdjustAssignmentTypeToDynamicIfNecessary(left.Type, forceDynamicResult), hasErrors: false); } else @@ -244,7 +246,56 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, var leftConversion = CreateConversion(node.Left, leftPlaceholder, best.LeftConversion, isCast: false, conversionGroupOpt: null, best.Signature.LeftType, diagnostics); return new BoundCompoundAssignmentOperator(node, bestSignature, left, rightConverted, - leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, resultKind, originalUserDefinedOperators, leftType, hasError); + leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, resultKind, originalUserDefinedOperators, AdjustAssignmentTypeToDynamicIfNecessary(left.Type, forceDynamicResult), hasError); + } + + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// + /// This helper takes care of the "reverting back to the indexer's type for the target" part. + /// See for the helper for the second part. + /// + private static BoundExpression AdjustAssignmentTargetForDynamic(BoundExpression target, out bool forceDynamicResult) + { + if (target is BoundIndexerAccess { Type.TypeKind: TypeKind.Dynamic, Indexer.Type.TypeKind: not TypeKind.Dynamic } indexerAccess) + { + Debug.Assert(!indexerAccess.Indexer.ReturnsByRef); + forceDynamicResult = true; + target = indexerAccess.Update( + indexerAccess.ReceiverOpt, + indexerAccess.InitialBindingReceiverIsSubjectToCloning, + indexerAccess.Indexer, + indexerAccess.Arguments, + indexerAccess.ArgumentNamesOpt, + indexerAccess.ArgumentRefKindsOpt, + indexerAccess.Expanded, + indexerAccess.ArgsToParamsOpt, + indexerAccess.DefaultArguments, + indexerAccess.Indexer.Type); + } + else + { + forceDynamicResult = false; + } + + return target; + } + + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// + /// This helper takes care of the "setting result type of the assignment to 'dynamic' type" part. + /// See helper for the first part. + /// + TypeSymbol AdjustAssignmentTypeToDynamicIfNecessary(TypeSymbol leftType, bool forceDynamicResult) + { + return forceDynamicResult ? Compilation.DynamicType : leftType; } /// @@ -2261,7 +2312,9 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS { operandSyntax.CheckDeconstructionCompatibleArgument(diagnostics); - BoundExpression operand = BindToNaturalType(BindValue(operandSyntax, diagnostics, BindValueKind.IncrementDecrement), diagnostics); + BoundExpression operand = BindToNaturalType(BindValue(operandSyntax, diagnostics, BindValueKind.IncrementDecrement, dynamificationOfAssignmentResultIsHandled: true), + diagnostics); + UnaryOperatorKind kind = SyntaxKindToUnaryOperatorKind(node.Kind()); // If the operand is bad, avoid generating cascading errors. @@ -2283,6 +2336,8 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS hasErrors: true); } + operand = AdjustAssignmentTargetForDynamic(operand, out bool forceDynamicResult); + // The operand has to be a variable, property or indexer, so it must have a type. var operandType = operand.Type; Debug.Assert((object)operandType != null); @@ -2369,7 +2424,7 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS resultConversion, resultKind, originalUserDefinedOperators, - operandType, + AdjustAssignmentTypeToDynamicIfNecessary(operandType, forceDynamicResult), hasErrors); } @@ -4134,8 +4189,11 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio { MessageID.IDS_FeatureCoalesceAssignmentExpression.CheckFeatureAvailability(diagnostics, node.OperatorToken); - BoundExpression leftOperand = BindValue(node.Left, diagnostics, BindValueKind.CompoundAssignment); + BoundExpression leftOperand = BindValue(node.Left, diagnostics, BindValueKind.CompoundAssignment, dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(leftOperand, diagnostics); + + leftOperand = AdjustAssignmentTargetForDynamic(leftOperand, out bool forceDynamicResult); + BoundExpression rightOperand = BindValue(node.Right, diagnostics, BindValueKind.RValue); // If either operand is bad, bail out preventing more cascading errors @@ -4170,7 +4228,9 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio { diagnostics.Add(node, useSiteInfo); var convertedRightOperand = CreateConversion(rightOperand, underlyingRightConversion, underlyingLeftType, diagnostics); - return new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, underlyingLeftType); + var result = new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, AdjustAssignmentTypeToDynamicIfNecessary(underlyingLeftType, forceDynamicResult)); + Debug.Assert(result.IsNullableValueTypeAssignment); + return result; } } @@ -4184,7 +4244,9 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio if (rightConversion.Exists) { var convertedRightOperand = CreateConversion(rightOperand, rightConversion, leftType, diagnostics); - return new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, leftType); + var result = new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, AdjustAssignmentTypeToDynamicIfNecessary(leftType, forceDynamicResult)); + Debug.Assert(!result.IsNullableValueTypeAssignment); + return result; } // a and b are incompatible and a compile-time error occurs @@ -4277,13 +4339,11 @@ private BoundExpression BindValueConditionalOperator(ConditionalExpressionSyntax return new BoundUnconvertedConditionalOperator(node, condition, trueExpr, falseExpr, constantValue, noCommonTypeError, hasErrors: constantValue?.IsBad == true); } - TypeSymbol type; bool hasErrors; if (bestType.IsErrorType()) { trueExpr = BindToNaturalType(trueExpr, diagnostics, reportNoTargetType: false); falseExpr = BindToNaturalType(falseExpr, diagnostics, reportNoTargetType: false); - type = bestType; hasErrors = true; } else @@ -4291,9 +4351,6 @@ private BoundExpression BindValueConditionalOperator(ConditionalExpressionSyntax trueExpr = GenerateConversionForAssignment(bestType, trueExpr, diagnostics); falseExpr = GenerateConversionForAssignment(bestType, falseExpr, diagnostics); hasErrors = trueExpr.HasAnyErrors || falseExpr.HasAnyErrors; - // If one of the conversions went wrong (e.g. return type of method group being converted - // didn't match), then we don't want to use bestType because it's not accurate. - type = hasErrors ? CreateErrorType() : bestType; } if (!hasErrors) @@ -4302,7 +4359,7 @@ private BoundExpression BindValueConditionalOperator(ConditionalExpressionSyntax hasErrors = constantValue != null && constantValue.IsBad; } - return new BoundConditionalOperator(node, isRef: false, condition, trueExpr, falseExpr, constantValue, naturalTypeOpt: type, wasTargetTyped: false, type, hasErrors); + return new BoundConditionalOperator(node, isRef: false, condition, trueExpr, falseExpr, constantValue, naturalTypeOpt: bestType, wasTargetTyped: false, bestType, hasErrors); } #nullable disable diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index d65ff54ae46fe..210cd7aabdc98 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1430,9 +1430,11 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD if (isRef) MessageID.IDS_FeatureRefReassignment.CheckFeatureAvailability(diagnostics, node.Right.GetFirstToken()); - var op1 = BindValue(node.Left, diagnostics, lhsKind); + var op1 = BindValue(node.Left, diagnostics, lhsKind, dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(op1, diagnostics); + op1 = AdjustAssignmentTargetForDynamic(op1, out bool forceDynamicResult); + var rhsKind = isRef ? GetRequiredRHSValueKindForRefAssignment(op1) : BindValueKind.RValue; var op2 = BindValue(rhsExpr, diagnostics, rhsKind); @@ -1443,7 +1445,10 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD op1 = InferTypeForDiscardAssignment((BoundDiscardExpression)op1, op2, diagnostics); } - return BindAssignment(node, op1, op2, isRef, diagnostics); + BoundAssignmentOperator result = BindAssignment(node, op1, op2, isRef, diagnostics); + result = result.Update(result.Left, result.Right, result.IsRef, AdjustAssignmentTypeToDynamicIfNecessary(result.Type, forceDynamicResult)); + + return result; } private static BindValueKind GetRequiredRHSValueKindForRefAssignment(BoundExpression boundLeft) diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 0bc9e18eb7df9..c2db44e853310 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -1205,7 +1205,7 @@ private void GetDisposalInfoForEnumerator(SyntaxNode syntax, ref ForEachEnumerat var argsBuilder = ArrayBuilder.GetInstance(patternDisposeMethod.ParameterCount); var argsToParams = default(ImmutableArray); - BindDefaultArgumentsAndParamsCollection( + BindDefaultArguments( syntax, patternDisposeMethod.Parameters, argsBuilder, @@ -1429,9 +1429,10 @@ private MethodArgumentInfo PerformForEachPatternOverloadResolution(SyntaxNode sy } else { + Debug.Assert(analyzedArguments.Arguments.Count == 0); var argsToParams = overloadResolutionResult.ValidResult.Result.ArgsToParamsOpt; var expanded = overloadResolutionResult.ValidResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; - BindDefaultArgumentsAndParamsCollection( + BindDefaultArguments( syntax, result.Parameters, analyzedArguments.Arguments, @@ -1883,7 +1884,7 @@ private MethodArgumentInfo BindDefaultArguments(MethodSymbol method, BoundExpres } ImmutableArray argsToParams = default; - BindDefaultArgumentsAndParamsCollection( + BindDefaultArguments( syntax, method.Parameters, argsBuilder, diff --git a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs index 6d6e983a1ef12..04ac043860e7d 100644 --- a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs +++ b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs @@ -258,6 +258,24 @@ private bool ContainsPlaceholderScope(BoundValuePlaceholderBase placeholder) public override BoundNode? Visit(BoundNode? node) { #if DEBUG + TrackVisit(node); +#endif + return base.Visit(node); + } + +#if DEBUG + protected override void BeforeVisitingSkippedBoundBinaryOperatorChildren(BoundBinaryOperator node) + { + TrackVisit(node); + } + + protected override void BeforeVisitingSkippedBoundCallChildren(BoundCall node) + { + TrackVisit(node); + } + + private void TrackVisit(BoundNode? node) + { if (node is BoundValuePlaceholderBase placeholder) { Debug.Assert(ContainsPlaceholderScope(placeholder)); @@ -267,14 +285,11 @@ private bool ContainsPlaceholderScope(BoundValuePlaceholderBase placeholder) if (_visited is { } && _visited.Count <= MaxTrackVisited) { bool added = _visited.Add(expr); - Debug.Assert(added); + Debug.Assert(added, $"Expression {expr} `{expr.Syntax}` visited more than once."); } } -#endif - return base.Visit(node); } -#if DEBUG private void AssertVisited(BoundExpression expr) { if (expr is BoundValuePlaceholderBase placeholder) @@ -283,7 +298,7 @@ private void AssertVisited(BoundExpression expr) } else if (_visited is { } && _visited.Count <= MaxTrackVisited) { - Debug.Assert(_visited.Contains(expr)); + Debug.Assert(_visited.Contains(expr), $"Expected {expr} `{expr.Syntax}` to be visited."); } } #endif @@ -539,6 +554,13 @@ private void RemoveLocalScopes(LocalSymbol local) return null; } + public override BoundNode? VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node) + { + base.VisitCompoundAssignmentOperator(node); + ValidateAssignment(node.Syntax, node.Left, node, isRef: false, _diagnostics); + return null; + } + public override BoundNode? VisitIsPatternExpression(BoundIsPatternExpression node) { this.Visit(node.Expression); @@ -652,13 +674,6 @@ protected override void VisitArguments(BoundCall node) _localScopeDepth, _diagnostics); } - -#if DEBUG - if (_visited is { } && _visited.Count <= MaxTrackVisited) - { - _visited.Add(node); - } -#endif } private void GetInterpolatedStringPlaceholders( diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs index 3332390061a99..abb608ffe3b20 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs @@ -194,7 +194,7 @@ protected override Conversion GetCollectionExpressionConversion( } if (elements.Length > 0 && - !_binder.HasCollectionExpressionApplicableAddMethod(syntax, targetType, elementType, addMethods: out _, BindingDiagnosticBag.Discarded)) + !_binder.HasCollectionExpressionApplicableAddMethod(syntax, targetType, addMethods: out _, BindingDiagnosticBag.Discarded)) { return Conversion.NoConversion; } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberAnalysisResult.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberAnalysisResult.cs index 30580a2a6e42f..f0a6c46065c3a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberAnalysisResult.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberAnalysisResult.cs @@ -14,8 +14,79 @@ namespace Microsoft.CodeAnalysis.CSharp { [SuppressMessage("Performance", "CA1067", Justification = "Equality not actually implemented")] - internal readonly struct MemberAnalysisResult + internal +#if !DEBUG + readonly +#endif + struct MemberAnalysisResult { +#if DEBUG + private readonly ImmutableArray _conversionsOpt; + public ImmutableArray ConversionsOpt + { + get + { + Debug.Assert(!_argumentsCoerced); + return _conversionsOpt; + } + private init + { + _conversionsOpt = value; + } + } + + /// + /// A bit vector representing whose true bits indicate indices of bad arguments + /// + /// + /// The capacity of this BitVector might not match the parameter count of the method overload being resolved. + /// For example, if a method overload has 5 parameters and the second parameter is the only bad parameter, then this + /// BitVector could end up with Capacity being 2 where BadArguments[0] is false and BadArguments[1] is true. + /// + private readonly BitVector _badArgumentsOpt; + public BitVector BadArgumentsOpt + { + get + { + Debug.Assert(!_argumentsCoerced); + return _badArgumentsOpt; + } + private init + { + _badArgumentsOpt = value; + } + } + + private readonly ImmutableArray _argsToParamsOpt; + public ImmutableArray ArgsToParamsOpt + { + get + { + Debug.Assert(!_argumentsCoerced); + return _argsToParamsOpt; + } + private init + { + _argsToParamsOpt = value; + } + } + + private readonly ImmutableArray _constraintFailureDiagnostics; + public ImmutableArray ConstraintFailureDiagnostics + { + get + { + Debug.Assert(!_argumentsCoerced); + return _constraintFailureDiagnostics; + } + private init + { + _constraintFailureDiagnostics = value; + } + } + + private bool _argumentsCoerced; +#else // put these first for better packing public readonly ImmutableArray ConversionsOpt; @@ -30,6 +101,7 @@ internal readonly struct MemberAnalysisResult public readonly BitVector BadArgumentsOpt; public readonly ImmutableArray ArgsToParamsOpt; public readonly ImmutableArray ConstraintFailureDiagnostics; +#endif public readonly int BadParameter; public readonly MemberResolutionKind Kind; @@ -325,5 +397,13 @@ internal static MemberAnalysisResult WrongCallingConvention() { return new MemberAnalysisResult(MemberResolutionKind.WrongCallingConvention); } + + [Conditional("DEBUG")] + public void ArgumentsWereCoerced() + { +#if DEBUG + _argumentsCoerced = true; +#endif + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index d5b9a3efeb4ae..385db1686b016 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -126,6 +126,7 @@ public enum Options : byte IsFunctionPointerResolution = 0b_00010000, IsExtensionMethodResolution = 0b_00100000, DynamicResolution = 0b_01000000, + DynamicConvertsToAnything = 0b_10000000, } // Perform overload resolution on the given method group, with the given arguments and @@ -870,6 +871,7 @@ private MemberAnalysisResult IsConstructorApplicableInNormalForm( hasAnyRefOmittedArgument: false, ignoreOpenTypes: false, completeResults: completeResults, + dynamicConvertsToAnything: false, useSiteInfo: ref useSiteInfo); } @@ -911,6 +913,7 @@ private MemberAnalysisResult IsConstructorApplicableInExpandedForm( hasAnyRefOmittedArgument: false, ignoreOpenTypes: false, completeResults: completeResults, + dynamicConvertsToAnything: false, useSiteInfo: ref useSiteInfo); Debug.Assert(!result.IsValid || result.Kind == MemberResolutionKind.ApplicableInExpandedForm); @@ -1063,6 +1066,7 @@ private void AddMemberToCandidateSet( arguments, allowRefOmittedArguments: (options & Options.AllowRefOmittedArguments) != 0, completeResults: completeResults, + dynamicConvertsToAnything: (options & Options.DynamicConvertsToAnything) != 0, useSiteInfo: ref useSiteInfo); if (PreferExpandedFormOverNormalForm(normalResult, expandedResult)) @@ -1167,7 +1171,9 @@ public static bool IsValidParams(Binder binder, Symbol member) } ParameterSymbol final = member.GetParameters().Last(); - if ((final.IsParamsArray && final.Type.IsSZArray()) || (final.IsParamsCollection && !final.Type.IsSZArray())) + if ((final.IsParamsArray && final.Type.IsSZArray()) || + (final.IsParamsCollection && !final.Type.IsSZArray() && + (binder.Compilation.LanguageVersion > LanguageVersion.CSharp12 || member.ContainingModule == binder.Compilation.SourceModule))) { return TryInferParamsCollectionIterationType(binder, final.OriginalDefinition.Type, out _); } @@ -1210,7 +1216,7 @@ public static bool TryInferParamsCollectionIterationType(Binder binder, TypeSymb return false; } - if (!binder.HasCollectionExpressionApplicableAddMethod(syntax, type, elementType.Type, addMethods: out _, BindingDiagnosticBag.Discarded)) + if (!binder.HasCollectionExpressionApplicableAddMethod(syntax, type, addMethods: out _, BindingDiagnosticBag.Discarded)) { return false; } @@ -2332,13 +2338,16 @@ private BetterResult BetterFunctionMember( TypeSymbol t1 = m1LeastOverriddenParameters[^1].Type; TypeSymbol t2 = m2LeastOverriddenParameters[^1].Type; - if (IsBetterParamsCollectionType(t1, t2, ref useSiteInfo)) + if (!Conversions.HasIdentityConversion(t1, t2)) { - return BetterResult.Left; - } - if (IsBetterParamsCollectionType(t2, t1, ref useSiteInfo)) - { - return BetterResult.Right; + if (IsBetterParamsCollectionType(t1, t2, ref useSiteInfo)) + { + return BetterResult.Left; + } + if (IsBetterParamsCollectionType(t2, t1, ref useSiteInfo)) + { + return BetterResult.Right; + } } } } @@ -2778,6 +2787,7 @@ private bool IsBetterCollectionExpressionConversion( TypeSymbol t2, CollectionExpressionTypeKind kind2, TypeSymbol elementType2, ref CompoundUseSiteInfo useSiteInfo) { + Debug.Assert(!Conversions.HasIdentityConversion(t1, t2)); // - T1 is System.ReadOnlySpan, and T2 is System.Span, and an implicit conversion exists from E1 to E2 if (kind1 is CollectionExpressionTypeKind.ReadOnlySpan && @@ -3702,6 +3712,7 @@ private MemberResolutionResult IsMemberApplicableInNormalForm( hasAnyRefOmittedArgument: hasAnyRefOmittedArgument, inferWithDynamic: (options & Options.InferWithDynamic) != 0, completeResults: completeResults, + dynamicConvertsToAnything: (options & Options.DynamicConvertsToAnything) != 0, useSiteInfo: ref useSiteInfo); // If we were producing complete results and had missing arguments, we pushed on in order to call IsApplicable for @@ -3721,6 +3732,7 @@ private MemberResolutionResult IsMemberApplicableInExpandedForm useSiteInfo) where TMember : Symbol { @@ -3763,6 +3775,7 @@ private MemberResolutionResult IsMemberApplicableInExpandedForm IsApplicable( bool hasAnyRefOmittedArgument, bool inferWithDynamic, bool completeResults, + bool dynamicConvertsToAnything, ref CompoundUseSiteInfo useSiteInfo) where TMember : Symbol { @@ -3898,6 +3912,7 @@ private MemberResolutionResult IsApplicable( hasAnyRefOmittedArgument: hasAnyRefOmittedArgument, ignoreOpenTypes: ignoreOpenTypes, completeResults: completeResults, + dynamicConvertsToAnything: dynamicConvertsToAnything, useSiteInfo: ref useSiteInfo); return new MemberResolutionResult(member, leastOverriddenMember, applicableResult, hasTypeArgumentsInferredFromFunctionType); } @@ -3967,6 +3982,7 @@ private MemberAnalysisResult IsApplicable( bool hasAnyRefOmittedArgument, bool ignoreOpenTypes, bool completeResults, + bool dynamicConvertsToAnything, ref CompoundUseSiteInfo useSiteInfo) { TypeWithAnnotations paramsElementTypeOpt; @@ -4079,9 +4095,15 @@ private MemberAnalysisResult IsApplicable( ignoreOpenTypes, ref useSiteInfo, forExtensionMethodThisArg, - hasInterpolatedStringRefMismatch); + hasInterpolatedStringRefMismatch, + dynamicConvertsToAnything); + + Debug.Assert( + !forExtensionMethodThisArg || + (!conversion.IsDynamic || + (ignoreOpenTypes && parameters.ParameterTypes[argumentPosition].Type.ContainsTypeParameter(parameterContainer: (MethodSymbol)candidate)))); - if (forExtensionMethodThisArg && !Conversions.IsValidExtensionMethodThisArgConversion(conversion)) + if (forExtensionMethodThisArg && !conversion.IsDynamic && !Conversions.IsValidExtensionMethodThisArgConversion(conversion)) { // Return early, without checking conversions of subsequent arguments, // if the instance argument is not convertible to the 'this' parameter, @@ -4150,7 +4172,8 @@ private Conversion CheckArgumentForApplicability( bool ignoreOpenTypes, ref CompoundUseSiteInfo useSiteInfo, bool forExtensionMethodThisArg, - bool hasInterpolatedStringRefMismatch) + bool hasInterpolatedStringRefMismatch, + bool dynamicConvertsToAnything) { // Spec 7.5.3.1 // For each argument in A, the parameter passing mode of the argument (i.e., value, ref, or out) is identical @@ -4191,7 +4214,9 @@ private Conversion CheckArgumentForApplicability( { var conversion = forExtensionMethodThisArg ? Conversions.ClassifyImplicitExtensionMethodThisArgConversion(argument, argument.Type, parameterType, ref useSiteInfo) : - Conversions.ClassifyImplicitConversionFromExpression(argument, parameterType, ref useSiteInfo); + ((!dynamicConvertsToAnything || !argument.Type.IsDynamic()) ? + Conversions.ClassifyImplicitConversionFromExpression(argument, parameterType, ref useSiteInfo) : + Conversion.ImplicitDynamic); Debug.Assert((!conversion.Exists) || conversion.IsImplicit, "ClassifyImplicitConversion should only return implicit conversions"); if (hasInterpolatedStringRefMismatch && !conversion.IsInterpolatedStringHandler) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs index 188d1893ffebb..45c569b13deaa 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs @@ -308,7 +308,7 @@ internal void ReportDiagnostics( // Otherwise, if there is any such method that has a bad argument conversion or out/ref mismatch // then the first such method found is the best bad method. - if (HadBadArguments(diagnostics, binder, name, arguments, symbols, location, binder.Flags, isMethodGroupConversion)) + if (HadBadArguments(diagnostics, binder, name, receiver, arguments, symbols, location, binder.Flags, isMethodGroupConversion)) { return; } @@ -519,8 +519,7 @@ internal void ReportDiagnostics( else { Debug.Assert(firstSupported.Member is MethodSymbol { Name: "Add" }); - int argumentOffset = arguments.IsExtensionMethodInvocation ? 1 : 0; - diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingAdd, location, arguments.Arguments[argumentOffset].Type, firstSupported.Member); + diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingAdd, location, receiver.Type); } } else @@ -1081,6 +1080,7 @@ private bool HadBadArguments( BindingDiagnosticBag diagnostics, Binder binder, string name, + BoundExpression receiver, AnalyzedArguments arguments, ImmutableArray symbols, Location location, @@ -1129,8 +1129,7 @@ private bool HadBadArguments( if (flags.Includes(BinderFlags.CollectionExpressionConversionValidation)) { - Debug.Assert(arguments.Arguments.Count == argumentOffset + 1); - diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingAdd, location, arguments.Arguments[argumentOffset].Type, method); + diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingAdd, location, receiver.Type); } else { diff --git a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs index e35930b124152..8fb8f0c4eb400 100644 --- a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs @@ -207,7 +207,7 @@ bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeI var argumentsBuilder = ArrayBuilder.GetInstance(disposeMethod.ParameterCount); ImmutableArray argsToParams = default; - originalBinder.BindDefaultArgumentsAndParamsCollection( + originalBinder.BindDefaultArguments( // If this is a using statement, then we want to use the whole `using (expr) { }` as the argument location. These arguments // will be represented in the IOperation tree and the "correct" node for them, given that they are an implicit invocation // at the end of the using statement, is on the whole using statement, not on the current expression. diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs index a8ed41a6f003c..8f0df23b1e56c 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs @@ -114,6 +114,7 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator var binary = (BoundBinaryOperator)node.Left; + BeforeVisitingSkippedBoundBinaryOperatorChildren(binary); rightOperands.Push(binary.Right); BoundExpression current = binary.Left; @@ -121,6 +122,7 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator while (current.Kind == BoundKind.BinaryOperator) { binary = (BoundBinaryOperator)current; + BeforeVisitingSkippedBoundBinaryOperatorChildren(binary); rightOperands.Push(binary.Right); current = binary.Left; } @@ -136,6 +138,10 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator return null; } + protected virtual void BeforeVisitingSkippedBoundBinaryOperatorChildren(BoundBinaryOperator node) + { + } + public sealed override BoundNode? VisitCall(BoundCall node) { if (node.ReceiverOpt is BoundCall receiver1) @@ -147,10 +153,13 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator node = receiver1; while (node.ReceiverOpt is BoundCall receiver2) { + BeforeVisitingSkippedBoundCallChildren(node); calls.Push(node); node = receiver2; } + BeforeVisitingSkippedBoundCallChildren(node); + VisitReceiver(node); do @@ -170,6 +179,10 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator return null; } + protected virtual void BeforeVisitingSkippedBoundCallChildren(BoundCall node) + { + } + /// /// Called only for the first (in evaluation order) in the chain. /// diff --git a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs index f856e82571782..9162afc9abc12 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs @@ -571,6 +571,7 @@ protected BoundBlock BindLambdaBody(LambdaSymbol lambdaSymbol, Binder lambdaBody Interlocked.Increment(ref data.LambdaBindingCount); } + Binder.RecordLambdaBinding(UnboundLambda.Syntax); return BindLambdaBodyCore(lambdaSymbol, lambdaBodyBinder, diagnostics); } diff --git a/src/Compilers/CSharp/Portable/CSharpExtensions.cs b/src/Compilers/CSharp/Portable/CSharpExtensions.cs index c42af7489034b..745c036c18d99 100644 --- a/src/Compilers/CSharp/Portable/CSharpExtensions.cs +++ b/src/Compilers/CSharp/Portable/CSharpExtensions.cs @@ -1638,6 +1638,26 @@ public static Conversion ClassifyConversion(this SemanticModel? semanticModel, i var csModel = semanticModel as CSharpSemanticModel; return csModel?.GetInterceptorMethod(node, cancellationToken); } + + /// + /// If cannot be intercepted syntactically, returns null. + /// Otherwise, returns an instance which can be used to intercept the call denoted by . + /// + [Experimental(RoslynExperiments.Interceptors, UrlFormat = RoslynExperiments.Interceptors_Url)] + public static InterceptableLocation? GetInterceptableLocation(this SemanticModel? semanticModel, InvocationExpressionSyntax node, CancellationToken cancellationToken = default) + { + var csModel = semanticModel as CSharpSemanticModel; + return csModel?.GetInterceptableLocation(node, cancellationToken); + } + + /// + /// Gets an attribute list syntax consisting of an InterceptsLocationAttribute, which intercepts the call referenced by parameter . + /// + [Experimental(RoslynExperiments.Interceptors, UrlFormat = RoslynExperiments.Interceptors_Url)] + public static string GetInterceptsLocationAttributeSyntax(this InterceptableLocation location) + { + return $"""[global::System.Runtime.CompilerServices.InterceptsLocationAttribute({location.Version}, "{location.Data}")]"""; + } #endregion } } diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 2c09d4efb99e5..e41aabbaa82c9 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5705,10 +5705,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Nullability of reference types in type of parameter doesn't match implemented member (possibly because of nullability attributes). - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. Nullability of reference types in value of type '{0}' doesn't match target type '{1}'. @@ -6851,7 +6851,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. The CollectionBuilderAttribute builder type must be a non-generic class or struct. @@ -6862,6 +6862,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A collection expression of type '{0}' cannot be used in this context because it may be exposed outside of the current scope. + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Record equality contract property '{0}' must have a get accessor. @@ -7890,6 +7896,24 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Modifiers cannot be placed on using declarations + + Cannot perform a dynamic invocation on an expression with type '{0}'. + + + The data argument to InterceptsLocationAttribute is not in the correct format. + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + 'yield return' should not be used in the body of a lock statement @@ -7908,4 +7932,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The '&' operator cannot be used on parameters or local variables in iterator methods. - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index f71459b318246..04b74a1dbd441 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -160,8 +161,12 @@ internal Conversions Conversions private ImmutableSegmentedDictionary> _mappedPathToSyntaxTree; /// Lazily caches SyntaxTrees by their path. Used to look up the syntax tree referenced by an interceptor. + /// Must be removed prior to interceptors stable release. private ImmutableSegmentedDictionary> _pathToSyntaxTree; + /// Lazily caches SyntaxTrees by their xxHash128 checksum. Used to look up the syntax tree referenced by an interceptor. + private ImmutableSegmentedDictionary, OneOrMany> _contentHashToSyntaxTree; + public override string Language { get @@ -1075,6 +1080,32 @@ ImmutableSegmentedDictionary> computeMappedPathToS } } + internal OneOrMany GetSyntaxTreesByContentHash(ReadOnlyMemory contentHash) + { + Debug.Assert(contentHash.Length == InterceptableLocation1.ContentHashLength); + + var contentHashToSyntaxTree = _contentHashToSyntaxTree; + if (contentHashToSyntaxTree.IsDefault) + { + RoslynImmutableInterlocked.InterlockedInitialize(ref _contentHashToSyntaxTree, computeHashToSyntaxTree()); + contentHashToSyntaxTree = _contentHashToSyntaxTree; + } + + return contentHashToSyntaxTree.TryGetValue(contentHash, out var value) ? value : OneOrMany.Empty; + + ImmutableSegmentedDictionary, OneOrMany> computeHashToSyntaxTree() + { + var builder = ImmutableSegmentedDictionary.CreateBuilder, OneOrMany>(ContentHashComparer.Instance); + foreach (var tree in SyntaxTrees) + { + var text = tree.GetText(); + var hash = text.GetContentHash().AsMemory(); + builder[hash] = builder.TryGetValue(hash, out var existing) ? existing.Add(tree) : OneOrMany.Create(tree); + } + return builder.ToImmutable(); + } + } + internal OneOrMany GetSyntaxTreesByPath(string path) { // We could consider storing this on SyntaxAndDeclarationManager instead, and updating it incrementally. @@ -2399,15 +2430,15 @@ internal void AddModuleInitializerMethod(MethodSymbol method) internal bool InterceptorsDiscoveryComplete; // NB: the 'Many' case for these dictionary values means there are duplicates. An error is reported for this after binding. - private ConcurrentDictionary<(string FilePath, int Line, int Character), OneOrMany<(Location AttributeLocation, MethodSymbol Interceptor)>>? _interceptions; + private ConcurrentDictionary<(string FilePath, int Position), OneOrMany<(Location AttributeLocation, MethodSymbol Interceptor)>>? _interceptions; - internal void AddInterception(string filePath, int line, int character, Location attributeLocation, MethodSymbol interceptor) + internal void AddInterception(string filePath, int position, Location attributeLocation, MethodSymbol interceptor) { Debug.Assert(!_declarationDiagnosticsFrozen); Debug.Assert(!InterceptorsDiscoveryComplete); var dictionary = LazyInitializer.EnsureInitialized(ref _interceptions); - dictionary.AddOrUpdate((filePath, line, character), + dictionary.AddOrUpdate((filePath, position), addValueFactory: static (key, newValue) => OneOrMany.Create(newValue), updateValueFactory: static (key, existingValues, newValue) => { @@ -2427,9 +2458,9 @@ internal void AddInterception(string filePath, int line, int character, Location factoryArgument: (AttributeLocation: attributeLocation, Interceptor: interceptor)); } - internal (Location AttributeLocation, MethodSymbol Interceptor)? TryGetInterceptor(Location? callLocation) + internal (Location AttributeLocation, MethodSymbol Interceptor)? TryGetInterceptor(SimpleNameSyntax? node) { - if (callLocation is null || !callLocation.IsInSource) + if (node is null) { return null; } @@ -2440,9 +2471,7 @@ internal void AddInterception(string filePath, int line, int character, Location return null; } - var callLineColumn = callLocation.GetLineSpan().Span.Start; - var key = (callLocation.SourceTree.FilePath, callLineColumn.Line, callLineColumn.Character); - + var key = (node.SyntaxTree.FilePath, node.Position); if (_interceptions.TryGetValue(key, out var interceptionsAtAGivenLocation) && interceptionsAtAGivenLocation is [var oneInterception]) { return oneInterception; diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index df844fd007afa..b79df55c6af72 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -3847,9 +3847,10 @@ private static void GetSymbolsAndResultKind(BoundIncrementOperator increment, ou { Debug.Assert((object)increment.MethodOpt == null && increment.OriginalUserDefinedOperatorsOpt.IsDefaultOrEmpty); UnaryOperatorKind op = increment.OperatorKind.Operator(); - symbols = OneOrMany.Create(new SynthesizedIntrinsicOperatorSymbol(increment.Operand.Type.StrippedType(), + TypeSymbol opType = increment.Operand.Type.StrippedType(); + symbols = OneOrMany.Create(new SynthesizedIntrinsicOperatorSymbol(opType, OperatorFacts.UnaryOperatorNameFromOperatorKind(op, isChecked: increment.OperatorKind.IsChecked()), - increment.Type.StrippedType())); + opType)); resultKind = increment.ResultKind; } } @@ -5207,13 +5208,40 @@ protected sealed override ISymbol GetDeclaredSymbolCore(SyntaxNode node, Cancell CheckSyntaxNode(node); - if (node.GetInterceptableNameSyntax() is { } nameSyntax && Compilation.TryGetInterceptor(nameSyntax.GetLocation()) is (_, MethodSymbol interceptor)) + if (node.GetInterceptableNameSyntax() is { } nameSyntax && Compilation.TryGetInterceptor(nameSyntax) is (_, MethodSymbol interceptor)) { return interceptor.GetPublicSymbol(); } return null; } + +#pragma warning disable RSEXPERIMENTAL002 // Internal usage of experimental API + public InterceptableLocation? GetInterceptableLocation(InvocationExpressionSyntax node, CancellationToken cancellationToken) + { + CheckSyntaxNode(node); + if (node.GetInterceptableNameSyntax() is not { } nameSyntax) + { + return null; + } + + return GetInterceptableLocationInternal(nameSyntax, cancellationToken); + } + + // Factored out for ease of test authoring, especially for scenarios involving unsupported syntax. + internal InterceptableLocation GetInterceptableLocationInternal(SyntaxNode nameSyntax, CancellationToken cancellationToken) + { + var tree = nameSyntax.SyntaxTree; + var text = tree.GetText(cancellationToken); + var path = tree.FilePath; + var checksum = text.GetContentHash(); + + var lineSpan = nameSyntax.Location.GetLineSpan().Span.Start; + var lineNumberOneIndexed = lineSpan.Line + 1; + var characterNumberOneIndexed = lineSpan.Character + 1; + + return new InterceptableLocation1(checksum, path, nameSyntax.Position, lineNumberOneIndexed, characterNumberOneIndexed); + } #nullable disable protected static SynthesizedPrimaryConstructor TryGetSynthesizedPrimaryConstructor(TypeDeclarationSyntax node, NamedTypeSymbol type) diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 2efcfc528e4fc..7e2ecb2809f45 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1772,7 +1772,11 @@ syntaxNode is ConstructorDeclarationSyntax constructorSyntax && buildIdentifierMapOfBindIdentifierTargets(syntaxNode, bodyBinder, out inMethodBinder, out identifierMap); #endif - BoundNode methodBody = bodyBinder.BindMethodBody(syntaxNode, diagnostics); + BoundNode methodBody = bodyBinder.BindWithLambdaBindingCountDiagnostics( + syntaxNode, + (object?)null, + diagnostics, + static (bodyBinder, syntaxNode, _, diagnostics) => bodyBinder.BindMethodBody(syntaxNode, diagnostics)); #if DEBUG assertBindIdentifierTargets(inMethodBinder, identifierMap, methodBody, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 7f65516069734..6953647dea65d 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2301,14 +2301,26 @@ internal enum ErrorCode ERR_ParamsCollectionMissingConstructor = 9228, ERR_NoModifiersOnUsing = 9229, + ERR_CannotDynamicInvokeOnExpression = 9230, + + ERR_InterceptsLocationDataInvalidFormat = 9231, + ERR_InterceptsLocationUnsupportedVersion = 9232, + ERR_InterceptsLocationDuplicateFile = 9233, + ERR_InterceptsLocationFileNotFound = 9234, + ERR_InterceptsLocationDataInvalidPosition = 9235, + INF_TooManyBoundLambdas = 9236, #endregion - WRN_BadYieldInLock = 9230, - ERR_BadYieldInUnsafe = 9231, - ERR_AddressOfInIterator = 9232, + WRN_BadYieldInLock = 9237, + ERR_BadYieldInUnsafe = 9238, + ERR_AddressOfInIterator = 9239, + + // Note: you will need to do the following after adding errors: + // 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs) // Note: you will need to do the following after adding warnings: // 1) Re-generate compiler code (eng\generate-compiler-code.cmd). + // 2) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs) } } diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index c599c2a5bb15f..ad6135e58ca7b 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -2431,6 +2431,13 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code) case ErrorCode.ERR_ParamsCollectionExtensionAddMethod: case ErrorCode.ERR_ParamsCollectionMissingConstructor: case ErrorCode.ERR_NoModifiersOnUsing: + case ErrorCode.ERR_CannotDynamicInvokeOnExpression: + case ErrorCode.ERR_InterceptsLocationDataInvalidFormat: + case ErrorCode.ERR_InterceptsLocationUnsupportedVersion: + case ErrorCode.ERR_InterceptsLocationDuplicateFile: + case ErrorCode.ERR_InterceptsLocationFileNotFound: + case ErrorCode.ERR_InterceptsLocationDataInvalidPosition: + case ErrorCode.INF_TooManyBoundLambdas: case ErrorCode.WRN_BadYieldInLock: case ErrorCode.ERR_BadYieldInUnsafe: case ErrorCode.ERR_AddressOfInIterator: diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs index f658b027842cb..c490fe273e06b 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs @@ -353,13 +353,12 @@ public override BoundNode VisitGotoStatement(BoundGotoStatement node) } else if (sourceStart > usingStart && targetStart < usingStart) { - // Backwards jump, so we must have already seen the label, or it must be a switch case label. If it is a switch case label, we know + // Backwards jump, so we must have already seen the label, or it must be a switch case label, or it might be in outer scope. If it is a switch case label, we know // that either the user received an error for having a using declaration at the top level in a switch statement, or the label is a valid // target to branch to. - Debug.Assert(_labelsDefined.ContainsKey(node.Label)); // Error if label and using are part of the same block - if (_labelsDefined[node.Label] == usingDecl.block) + if (_labelsDefined.TryGetValue(node.Label, out BoundNode target) && target == usingDecl.block) { Diagnostics.Add(ErrorCode.ERR_GoToBackwardJumpOverUsingVar, sourceLocation); break; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 5f7eacbfddf06..91dc066983043 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4081,7 +4081,8 @@ void setAnalyzedNullabilityAsContinuation( if (symbol != null) { - Debug.Assert(TypeSymbol.Equals(objectInitializer.Type, symbol.GetTypeOrReturnType().Type, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); + Debug.Assert(TypeSymbol.Equals(objectInitializer.Type, symbol.GetTypeOrReturnType().Type, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes) || + (symbol is PropertySymbol { IsIndexer: true } && objectInitializer.Type.IsDynamic())); symbol = AsMemberOfType(containingType, symbol); } @@ -5440,20 +5441,35 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly LearnFromNonNullTest(leftOperand, ref leftState); LearnFromNullTest(leftOperand, ref this.State); + var adjustedNodeType = node.Type; + if (LocalRewriter.ShouldConvertResultOfAssignmentToDynamic(node, leftOperand)) + { + Debug.Assert(leftOperand.Type is not null); + + if (node.IsNullableValueTypeAssignment) + { + adjustedNodeType = leftOperand.Type.GetNullableUnderlyingType(); + } + else + { + adjustedNodeType = leftOperand.Type; + } + } + // If we are assigning to a nullable value type variable, set the top-level state of // the LHS first, then change the slot to the Value property of the LHS to simulate // assignment of the RHS and update the nullable state of the underlying value type. if (node.IsNullableValueTypeAssignment) { Debug.Assert(targetType.Type.ContainsErrorType() || - node.Type?.ContainsErrorType() == true || - TypeSymbol.Equals(targetType.Type.GetNullableUnderlyingType(), node.Type, TypeCompareKind.AllIgnoreOptions)); + adjustedNodeType?.ContainsErrorType() == true || + TypeSymbol.Equals(targetType.Type.GetNullableUnderlyingType(), adjustedNodeType, TypeCompareKind.AllIgnoreOptions)); if (leftSlot > 0) { SetState(ref this.State, leftSlot, NullableFlowState.NotNull); leftSlot = GetNullableOfTValueSlot(targetType.Type, leftSlot, out _); } - targetType = TypeWithAnnotations.Create(node.Type, NullableAnnotation.NotAnnotated); + targetType = TypeWithAnnotations.Create(adjustedNodeType, NullableAnnotation.NotAnnotated); } TypeWithState rightResult = VisitOptionalImplicitConversion(rightOperand, targetType, useLegacyWarnings: UseLegacyWarnings(leftOperand), trackMembers: false, AssignmentKind.Assignment); @@ -5462,10 +5478,49 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly Join(ref this.State, ref leftState); TypeWithState resultType = TypeWithState.Create(targetType.Type, rightResult.State); - SetResultType(node, resultType); + + if (adjustedNodeType != (object?)node.Type) + { + Debug.Assert(adjustedNodeType is not null); + SetDynamicResult(node, resultType); + } + else + { + SetResultType(node, resultType); + } + return null; } + /// + /// When an operation on an indexer with dynamic argument is resolved statically, + /// in some scenarios result type of the operation is set to 'dynamic' type. + /// + /// This helper takes care of the setting result type to 'dynamic' type. + /// + private void SetDynamicResult(BoundExpression node, TypeWithState sourceType) + { + Debug.Assert(node.Type is not null); + Debug.Assert(node.Type.IsDynamic()); + Debug.Assert(sourceType.Type is not null); + Debug.Assert(!sourceType.Type.IsDynamic()); + Debug.Assert(!sourceType.Type.IsVoidType()); + + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + + SetResultType(node, + VisitConversion( + conversionOpt: null, + conversionOperand: node, + _conversions.ClassifyConversionFromExpressionType(sourceType.Type, node.Type, isChecked: false, ref discardedUseSiteInfo), + targetTypeWithNullability: TypeWithAnnotations.Create(node.Type, NullableAnnotation.Annotated), + operandType: sourceType, + checkConversion: false, + fromExplicitCast: false, + useLegacyWarnings: false, + AssignmentKind.Assignment)); + } + public override BoundNode? VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { Debug.Assert(!IsConditionalState); @@ -5812,7 +5867,7 @@ void makeAndAdjustReceiverSlot(BoundExpression receiver) TypeSymbol? resultType; bool wasTargetTyped = node is BoundConditionalOperator { WasTargetTyped: true }; - if (node.HasErrors || wasTargetTyped) + if (wasTargetTyped) { resultType = null; } @@ -5840,6 +5895,7 @@ void makeAndAdjustReceiverSlot(BoundExpression receiver) if (resultType is null) { + Debug.Assert(!wasTargetTyped); if (!wasTargetTyped) { // This can happen when we're inferring the return type of a lambda or visiting a node without diagnostics like @@ -6054,7 +6110,7 @@ private static BoundExpression CreatePlaceholderIfNecessary(BoundExpression expr VisitResult? extensionReceiverResult = null; while (true) { - ReinferMethodAndVisitArguments(node, receiverType, firstArgumentResult: extensionReceiverResult); + reinferMethodAndVisitArguments(node, receiverType, firstArgumentResult: extensionReceiverResult); receiver = node; if (!calls.TryPop(out node!)) @@ -6090,7 +6146,7 @@ private static BoundExpression CreatePlaceholderIfNecessary(BoundExpression expr else { TypeWithState receiverType = visitAndCheckReceiver(node); - ReinferMethodAndVisitArguments(node, receiverType); + reinferMethodAndVisitArguments(node, receiverType); } return null; @@ -6129,35 +6185,44 @@ TypeWithState visitAndCheckReceiver(BoundCall node) return receiverType; } - } - private void ReinferMethodAndVisitArguments(BoundCall node, TypeWithState receiverType, VisitResult? firstArgumentResult = null) - { - var method = node.Method; - ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt; - if (!receiverType.HasNullType) + void reinferMethodAndVisitArguments(BoundCall node, TypeWithState receiverType, VisitResult? firstArgumentResult = null) { - // Update method based on inferred receiver type. - method = (MethodSymbol)AsMemberOfType(receiverType.Type, method); - } + var method = node.Method; + ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt; + if (!receiverType.HasNullType) + { + // Update method based on inferred receiver type. + method = (MethodSymbol)AsMemberOfType(receiverType.Type, method); + } - ImmutableArray results; - bool returnNotNull; - (method, results, returnNotNull) = VisitArguments(node, node.Arguments, refKindsOpt, method!.Parameters, node.ArgsToParamsOpt, node.DefaultArguments, - node.Expanded, node.InvokedAsExtensionMethod, method, firstArgumentResult: firstArgumentResult); + ImmutableArray results; + bool returnNotNull; + (method, results, returnNotNull) = VisitArguments(node, node.Arguments, refKindsOpt, method!.Parameters, node.ArgsToParamsOpt, node.DefaultArguments, + node.Expanded, node.InvokedAsExtensionMethod, method, firstArgumentResult: firstArgumentResult); - ApplyMemberPostConditions(node.ReceiverOpt, method); + ApplyMemberPostConditions(node.ReceiverOpt, method); - LearnFromEqualsMethod(method, node, receiverType, results); + LearnFromEqualsMethod(method, node, receiverType, results); - var returnState = GetReturnTypeWithState(method); - if (returnNotNull) - { - returnState = returnState.WithNotNullState(); - } + var returnState = GetReturnTypeWithState(method); + if (returnNotNull) + { + returnState = returnState.WithNotNullState(); + } - SetResult(node, returnState, method.ReturnTypeWithAnnotations); - SetUpdatedSymbol(node, node.Method, method); + if (node.Type.IsDynamic() && !node.Method.ReturnType.IsDynamic()) + { + Debug.Assert(!node.Method.ReturnsByRef); + SetDynamicResult(node, returnState); + } + else + { + SetResult(node, returnState, method.ReturnTypeWithAnnotations); + } + + SetUpdatedSymbol(node, node.Method, method); + } } private void LearnFromEqualsMethod(MethodSymbol method, BoundCall node, TypeWithState receiverType, ImmutableArray results) @@ -6718,7 +6783,13 @@ private ImmutableArray VisitArguments( (ParameterSymbol? parameter, TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations, bool isExpandedParamsArgument) = GetCorrespondingParameter(i, parametersOpt, argsToParamsOpt, expanded, ref paramsIterationType); - if (parameter is null) + if (// This is known to happen for certain error scenarios, because + // the parameter matching logic above is not as flexible as the one we use in `Binder.BuildArgumentsForErrorRecovery` + // so we may end up with a pending conversion completion for an argument apparently without a corresponding parameter. + parameter is null || + // In error recovery with named arguments, target-typing cannot work as we can get a different parameter type + // from our GetCorrespondingParameter logic than Binder.BuildArgumentsForErrorRecovery does. + node is BoundCall { HasErrors: true, ArgumentNamesOpt.IsDefaultOrEmpty: false, ArgsToParamsOpt.IsDefault: true }) { if (IsTargetTypedExpression(argumentNoConversion) && _targetTypedAnalysisCompletionOpt?.TryGetValue(argumentNoConversion, out var completion) is true) { @@ -6728,10 +6799,7 @@ private ImmutableArray VisitArguments( completion(TypeWithAnnotations.Create(argument.Type)); TargetTypedAnalysisCompletion.Remove(argumentNoConversion); - // This is known to happen for certain error scenarios, because - // the parameter matching logic above is not as flexible as the one we use in `Binder.BuildArgumentsForErrorRecovery` - // so we may end up with a pending conversion completion for an argument apparently without a corresponding parameter. - Debug.Assert(method is ErrorMethodSymbol); + Debug.Assert(parameter is not null || method is ErrorMethodSymbol); } continue; } @@ -9775,7 +9843,7 @@ private void VisitThisOrBaseReference(BoundExpression node) } else { - SetResult(node, TypeWithState.Create(leftLValueType.Type, rightState.State), leftLValueType); + SetResultConveringToDynamicIfNecessary(node, left, TypeWithState.Create(leftLValueType.Type, rightState.State), leftLValueType); } AdjustSetValue(left, ref rightState); @@ -9785,6 +9853,21 @@ private void VisitThisOrBaseReference(BoundExpression node) return null; } + private void SetResultConveringToDynamicIfNecessary(BoundExpression originalAssignment, BoundExpression assignmentTarget, TypeWithState resultType, TypeWithAnnotations lvalueType) + { + Debug.Assert(originalAssignment.Type is not null); + Debug.Assert(assignmentTarget.Type is not null); + + if (LocalRewriter.ShouldConvertResultOfAssignmentToDynamic(originalAssignment, assignmentTarget)) + { + SetDynamicResult(originalAssignment, resultType); + } + else + { + SetResult(originalAssignment, resultType, lvalueType); + } + } + private bool IsPropertyOutputMoreStrictThanInput(PropertySymbol property) { var type = property.TypeWithAnnotations; @@ -10294,7 +10377,7 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress { var op = node.OperatorKind.Operator(); TypeWithState resultType = (op == UnaryOperatorKind.PrefixIncrement || op == UnaryOperatorKind.PrefixDecrement) ? resultOfIncrementType : operandType; - SetResultType(node, resultType); + SetResultConveringToDynamicIfNecessary(node, node.Operand, resultType, resultType.ToTypeWithAnnotations(compilation)); setResult = true; TrackNullableStateForAssignment(node, targetType: operandLvalue, targetSlot: MakeSlot(node.Operand), valueType: resultOfIncrementType); @@ -10362,7 +10445,7 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress // Handle `[DisallowNull]` on LHS operand (final assignment target). CheckDisallowedNullAssignment(resultTypeWithState, leftArgumentAnnotations, node.Syntax); - SetResultType(node, resultTypeWithState); + SetResultConveringToDynamicIfNecessary(node, node.Left, resultTypeWithState, resultTypeWithState.ToTypeWithAnnotations(compilation)); AdjustSetValue(node.Left, ref resultTypeWithState); Debug.Assert(MakeSlot(node) == -1); @@ -10500,7 +10583,17 @@ private TypeWithAnnotations GetDeclaredParameterResult(ParameterSymbol parameter VisitArguments(node, node.Arguments, node.ArgumentRefKindsOpt, indexer, node.ArgsToParamsOpt, node.DefaultArguments, node.Expanded); var resultType = ApplyUnconditionalAnnotations(indexer.TypeWithAnnotations.ToTypeWithState(), GetRValueAnnotations(indexer)); - SetResult(node, resultType, indexer.TypeWithAnnotations); + + if (node.Type.IsDynamic() && !node.Indexer.Type.IsDynamic()) + { + Debug.Assert(!node.Indexer.ReturnsByRef); + SetDynamicResult(node, resultType); + } + else + { + SetResult(node, resultType, indexer.TypeWithAnnotations); + } + SetUpdatedSymbol(node, node.Indexer, indexer); return null; } diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs index 008a81230a7b2..ac6ebf95db21f 100644 --- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs @@ -370,6 +370,7 @@ public static bool IsInfo(ErrorCode code) switch (code) { case ErrorCode.INF_UnableToLoadSomeTypesInAnalyzer: + case ErrorCode.INF_TooManyBoundLambdas: return true; default: return false; diff --git a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/ExpressionLambdaRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/ExpressionLambdaRewriter.cs index aea66806e9933..37849dc7d66ff 100644 --- a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/ExpressionLambdaRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/ExpressionLambdaRewriter.cs @@ -339,7 +339,7 @@ private BoundExpression Expressions(ImmutableArray expressions) private BoundExpression VisitArrayCreation(BoundArrayCreation node) { var arrayType = (ArrayTypeSymbol)node.Type; - var boundType = _bound.Typeof(arrayType.ElementType); + var boundType = _bound.Typeof(arrayType.ElementType, _bound.WellKnownType(WellKnownType.System_Type)); if (node.InitializerOpt != null) { if (arrayType.IsSZArray) @@ -374,7 +374,7 @@ private BoundExpression VisitAsOperator(BoundAsOperator node) node = node.Update(operand, node.TargetType, node.OperandPlaceholder, node.OperandConversion, node.Type); } - return ExprFactory("TypeAs", Visit(node.Operand), _bound.Typeof(node.Type)); + return ExprFactory("TypeAs", Visit(node.Operand), _bound.Typeof(node.Type, _bound.WellKnownType(WellKnownType.System_Type))); } private BoundExpression VisitBaseReference(BoundBaseReference node) @@ -502,8 +502,10 @@ private BoundExpression MakeBinary(MethodSymbol methodOpt, TypeSymbol type, bool { return ((object)methodOpt == null) ? ExprFactory(opName, loweredLeft, loweredRight) : - requiresLifted ? ExprFactory(opName, loweredLeft, loweredRight, _bound.Literal(isLifted && !TypeSymbol.Equals(methodOpt.ReturnType, type, TypeCompareKind.ConsiderEverything2)), _bound.MethodInfo(methodOpt)) : - ExprFactory(opName, loweredLeft, loweredRight, _bound.MethodInfo(methodOpt)); + requiresLifted ? + ExprFactory(opName, loweredLeft, loweredRight, _bound.Literal(isLifted && !TypeSymbol.Equals(methodOpt.ReturnType, type, TypeCompareKind.ConsiderEverything2)), + _bound.MethodInfo(methodOpt, _bound.WellKnownType(WellKnownType.System_Reflection_MethodInfo))) : + ExprFactory(opName, loweredLeft, loweredRight, _bound.MethodInfo(methodOpt, _bound.WellKnownType(WellKnownType.System_Reflection_MethodInfo))); } private TypeSymbol PromotedType(TypeSymbol underlying) @@ -577,7 +579,7 @@ private BoundExpression VisitCall(BoundCall node) return ExprFactory( "Call", method.RequiresInstanceReceiver ? Visit(node.ReceiverOpt) : _bound.Null(ExpressionType), - _bound.MethodInfo(method), + _bound.MethodInfo(method, _bound.WellKnownType(WellKnownType.System_Reflection_MethodInfo)), Expressions(node.Arguments)); } } @@ -638,7 +640,10 @@ private BoundExpression VisitConversion(BoundConversion node) var e1 = requireAdditionalCast ? Convert(Visit(node.Operand), node.Operand.Type, method.Parameters[0].Type, node.Checked, false) : Visit(node.Operand); - var e2 = ExprFactory(node.Checked && SyntaxFacts.IsCheckedOperator(method.Name) ? "ConvertChecked" : "Convert", e1, _bound.Typeof(resultType), _bound.MethodInfo(method)); + var e2 = ExprFactory(node.Checked && SyntaxFacts.IsCheckedOperator(method.Name) ? + "ConvertChecked" : + "Convert", e1, _bound.Typeof(resultType, _bound.WellKnownType(WellKnownType.System_Type)), + _bound.MethodInfo(method, _bound.WellKnownType(WellKnownType.System_Reflection_MethodInfo))); return Convert(e2, resultType, node.Type, node.Checked, false); } case ConversionKind.ImplicitReference: @@ -674,7 +679,7 @@ private BoundExpression Convert(BoundExpression operand, TypeSymbol oldType, Typ private BoundExpression Convert(BoundExpression expr, TypeSymbol type, bool isChecked) { - return ExprFactory(isChecked ? "ConvertChecked" : "Convert", expr, _bound.Typeof(type)); + return ExprFactory(isChecked ? "ConvertChecked" : "Convert", expr, _bound.Typeof(type, _bound.WellKnownType(WellKnownType.System_Type))); } private BoundExpression DelegateCreation(BoundExpression receiver, MethodSymbol method, TypeSymbol delegateType, bool requiresInstanceReceiver) @@ -687,14 +692,16 @@ private BoundExpression DelegateCreation(BoundExpression receiver, MethodSymbol if ((object)createDelegate != null) { // beginning in 4.5, we do it this way - unquoted = _bound.Call(_bound.MethodInfo(method), createDelegate, _bound.Typeof(delegateType), receiver); + unquoted = _bound.Call(_bound.MethodInfo(method, createDelegate.ContainingType), createDelegate, _bound.Typeof(delegateType, createDelegate.Parameters[0].Type), receiver); } else { // 4.0 and earlier we do it this way - //createDelegate = (MethodSymbol)Bound.WellKnownMember(WellKnownMember.System_Delegate__CreateDelegate); - //operand = Bound.Call(nullObject, createDelegate, Bound.Typeof(node.Type), receiver, Bound.MethodInfo(method)); - unquoted = _bound.StaticCall(_bound.SpecialType(SpecialType.System_Delegate), "CreateDelegate", _bound.Typeof(delegateType), receiver, _bound.MethodInfo(method)); + createDelegate = _bound.SpecialMethod(SpecialMember.System_Delegate__CreateDelegate); + unquoted = _bound.Call(null, createDelegate, + _bound.Typeof(delegateType, createDelegate.Parameters[0].Type), + receiver, + _bound.MethodInfo(method, createDelegate.Parameters[2].Type)); } // NOTE: we visit the just-built node, which has not yet been visited. This is not the usual order @@ -742,7 +749,7 @@ private BoundExpression VisitIsOperator(BoundIsOperator node) operand = _bound.Null(_objectType); } - return ExprFactory("TypeIs", Visit(operand), _bound.Typeof(node.TargetType.Type)); + return ExprFactory("TypeIs", Visit(operand), _bound.Typeof(node.TargetType.Type, _bound.WellKnownType(WellKnownType.System_Type))); } private BoundExpression VisitLambda(BoundLambda node) @@ -765,7 +772,7 @@ private BoundExpression VisitLambdaInternal(BoundLambda node) parameters.Add(parameterReference); var parameter = ExprFactory( "Parameter", - _bound.Typeof(_typeMap.SubstituteType(p.Type).Type), _bound.Literal(p.Name)); + _bound.Typeof(_typeMap.SubstituteType(p.Type).Type, _bound.WellKnownType(WellKnownType.System_Type)), _bound.Literal(p.Name)); initializers.Add(_bound.AssignmentExpression(parameterReference, parameter)); _parameterMap[p] = parameterReference; } @@ -788,7 +795,7 @@ private BoundExpression VisitLambdaInternal(BoundLambda node) private BoundExpression VisitNewT(BoundNewT node) { - return VisitObjectCreationContinued(ExprFactory("New", _bound.Typeof(node.Type)), node.InitializerExpressionOpt); + return VisitObjectCreationContinued(ExprFactory("New", _bound.Typeof(node.Type, _bound.WellKnownType(WellKnownType.System_Type))), node.InitializerExpressionOpt); } private BoundExpression VisitNullCoalescingOperator(BoundNullCoalescingOperator node) @@ -813,7 +820,7 @@ private BoundExpression MakeConversionLambda(Conversion conversion, TypeSymbol f ParameterSymbol lambdaParameter = _bound.SynthesizedParameter(fromType, parameterName); var param = _bound.SynthesizedLocal(ParameterExpressionType); var parameterReference = _bound.Local(param); - var parameter = ExprFactory("Parameter", _bound.Typeof(fromType), _bound.Literal(parameterName)); + var parameter = ExprFactory("Parameter", _bound.Typeof(fromType, _bound.WellKnownType(WellKnownType.System_Type)), _bound.Literal(parameterName)); _parameterMap[lambdaParameter] = parameterReference; var convertedValue = Visit(_bound.Convert(toType, _bound.Parameter(lambdaParameter), conversion)); _parameterMap.Remove(lambdaParameter); @@ -834,7 +841,7 @@ private BoundExpression InitializerMemberSetter(Symbol symbol) case SymbolKind.Field: return _bound.Convert(MemberInfoType, _bound.FieldInfo((FieldSymbol)symbol)); case SymbolKind.Property: - return _bound.MethodInfo(((PropertySymbol)symbol).GetOwnOrInheritedSetMethod()); + return _bound.MethodInfo(((PropertySymbol)symbol).GetOwnOrInheritedSetMethod(), _bound.WellKnownType(WellKnownType.System_Reflection_MethodInfo)); case SymbolKind.Event: return _bound.Convert(MemberInfoType, _bound.FieldInfo(((EventSymbol)symbol).AssociatedField)); default: @@ -849,7 +856,7 @@ private BoundExpression InitializerMemberGetter(Symbol symbol) case SymbolKind.Field: return _bound.Convert(MemberInfoType, _bound.FieldInfo((FieldSymbol)symbol)); case SymbolKind.Property: - return _bound.MethodInfo(((PropertySymbol)symbol).GetOwnOrInheritedGetMethod()); + return _bound.MethodInfo(((PropertySymbol)symbol).GetOwnOrInheritedGetMethod(), _bound.WellKnownType(WellKnownType.System_Reflection_MethodInfo)); case SymbolKind.Event: return _bound.Convert(MemberInfoType, _bound.FieldInfo(((EventSymbol)symbol).AssociatedField)); default: @@ -917,7 +924,7 @@ private BoundExpression VisitInitializer(BoundExpression node, out InitializerKi // Dynamic calls are not allowed in ETs, an error is reported in diagnostics pass. foreach (BoundCollectionElementInitializer i in ci.Initializers) { - BoundExpression elementInit = ExprFactory("ElementInit", _bound.MethodInfo(i.AddMethod), Expressions(i.Arguments)); + BoundExpression elementInit = ExprFactory("ElementInit", _bound.MethodInfo(i.AddMethod, _bound.WellKnownType(WellKnownType.System_Reflection_MethodInfo)), Expressions(i.Arguments)); builder.Add(elementInit); } @@ -966,7 +973,7 @@ private BoundExpression VisitObjectCreationExpressionInternal(BoundObjectCreatio (node.Arguments.Length == 0 && !node.Type.IsStructType()) || node.Constructor.IsDefaultValueTypeConstructor()) { - return ExprFactory("New", _bound.Typeof(node.Type)); + return ExprFactory("New", _bound.Typeof(node.Type, _bound.WellKnownType(WellKnownType.System_Type))); } var ctor = _bound.ConstructorInfo(node.Constructor); @@ -977,7 +984,7 @@ private BoundExpression VisitObjectCreationExpressionInternal(BoundObjectCreatio var membersBuilder = ArrayBuilder.GetInstance(); for (int i = 0; i < node.Arguments.Length; i++) { - membersBuilder.Add(_bound.MethodInfo(AnonymousTypeManager.GetAnonymousTypeProperty(anonType, i).GetMethod)); + membersBuilder.Add(_bound.MethodInfo(AnonymousTypeManager.GetAnonymousTypeProperty(anonType, i).GetMethod, _bound.WellKnownType(WellKnownType.System_Reflection_MethodInfo))); } return ExprFactory("New", ctor, args, _bound.ArrayOrEmpty(MemberInfoType, membersBuilder.ToImmutableAndFree())); @@ -1029,7 +1036,7 @@ private BoundExpression VisitPropertyAccess(BoundPropertyAccess node) receiver = this.Convert(receiver, getMethod.ReceiverType, isChecked: false); } - return ExprFactory("Property", receiver, _bound.MethodInfo(getMethod)); + return ExprFactory("Property", receiver, _bound.MethodInfo(getMethod, _bound.WellKnownType(WellKnownType.System_Reflection_MethodInfo))); } private static BoundExpression VisitSizeOfOperator(BoundSizeOfOperator node) @@ -1080,7 +1087,7 @@ private BoundExpression VisitUnaryOperator(BoundUnaryOperator node) return ((object)node.MethodOpt == null) ? ExprFactory(opname, loweredArg) - : ExprFactory(opname, loweredArg, _bound.MethodInfo(node.MethodOpt)); + : ExprFactory(opname, loweredArg, _bound.MethodInfo(node.MethodOpt, _bound.WellKnownType(WellKnownType.System_Reflection_MethodInfo))); } // ====================================================== @@ -1100,7 +1107,7 @@ private BoundExpression Constant(BoundExpression node) return ExprFactory( "Constant", _bound.Convert(_objectType, node), - _bound.Typeof(node.Type)); + _bound.Typeof(node.Type, _bound.WellKnownType(WellKnownType.System_Type))); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index 6dda3cb81f2f4..59cd4fd4afe8a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -247,7 +247,7 @@ private PEModuleBuilder? EmitModule { Debug.Assert(!nameofOperator.WasCompilerGenerated); var nameofIdentiferSyntax = (IdentifierNameSyntax)((InvocationExpressionSyntax)nameofOperator.Syntax).Expression; - if (this._compilation.TryGetInterceptor(nameofIdentiferSyntax.Location) is not null) + if (this._compilation.TryGetInterceptor(nameofIdentiferSyntax) is not null) { this._diagnostics.Add(ErrorCode.ERR_InterceptorCannotInterceptNameof, nameofIdentiferSyntax.Location); } @@ -645,6 +645,8 @@ private static bool TryGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember spe public override BoundNode VisitTypeOfOperator(BoundTypeOfOperator node) { + Debug.Assert(node.Type.ExtendedSpecialType == InternalSpecialType.System_Type || + TypeSymbol.Equals(node.Type, _compilation.GetWellKnownType(WellKnownType.System_Type), TypeCompareKind.AllIgnoreOptions)); Debug.Assert(node.GetTypeFromHandle is null); var sourceType = (BoundTypeExpression?)this.Visit(node.SourceType); @@ -653,11 +655,24 @@ public override BoundNode VisitTypeOfOperator(BoundTypeOfOperator node) // Emit needs this helper MethodSymbol? getTypeFromHandle; - if (!TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_Type__GetTypeFromHandle, out getTypeFromHandle)) + bool tryGetResult; + + if (node.Type.ExtendedSpecialType == InternalSpecialType.System_Type) + { + tryGetResult = TryGetSpecialTypeMethod(node.Syntax, SpecialMember.System_Type__GetTypeFromHandle, out getTypeFromHandle); + } + else + { + tryGetResult = TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_Type__GetTypeFromHandle, out getTypeFromHandle); + } + + if (!tryGetResult) { return new BoundTypeOfOperator(node.Syntax, sourceType, null, type, hasErrors: true); } + Debug.Assert(getTypeFromHandle is not null); + Debug.Assert(TypeSymbol.Equals(type, getTypeFromHandle.ReturnType, TypeCompareKind.AllIgnoreOptions)); return node.Update(sourceType, getTypeFromHandle, type); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs index ee4a1091d7b32..9fcab11c2c2f6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs @@ -81,14 +81,56 @@ private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bo break; } - return MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, node.Type, used); + var result = MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, used); + + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, left, result, used); + + Debug.Assert(used || result.Type?.IsVoidType() == true || + (left switch { BoundIndexerAccess indexer => indexer.Indexer, BoundPropertyAccess property => property.PropertySymbol, _ => null }) is not PropertySymbol prop || + prop.GetOwnOrInheritedSetMethod() is null); + + Debug.Assert(result.Type?.IsVoidType() == true || TypeSymbol.Equals(result.Type, node.Type, TypeCompareKind.AllIgnoreOptions)); + + return result; + } + + private static bool ShouldConvertResultOfAssignmentToDynamic(TypeSymbol? assignmentResultType, BoundExpression target) + { + if (assignmentResultType?.IsDynamic() == true && target is BoundIndexerAccess { Type.TypeKind: not TypeKind.Dynamic } indexerAccess) + { + Debug.Assert(!indexerAccess.Indexer.Type.IsDynamic()); + Debug.Assert(!indexerAccess.Indexer.ReturnsByRef); + + return true; + } + + return false; + } + + internal static bool ShouldConvertResultOfAssignmentToDynamic(BoundExpression assignment, BoundExpression target) + { + Debug.Assert(assignment is BoundAssignmentOperator or BoundIncrementOperator or BoundCompoundAssignmentOperator or BoundNullCoalescingAssignmentOperator); + return ShouldConvertResultOfAssignmentToDynamic(assignment.Type, target); + } + + private BoundExpression ConvertResultOfAssignmentToDynamicIfNecessary(BoundExpression originalAssignment, BoundExpression originalTarget, BoundExpression result, bool used) + { + Debug.Assert(originalAssignment.Type is not null); + if (used && ShouldConvertResultOfAssignmentToDynamic(originalAssignment, originalTarget)) + { + Debug.Assert(result.Type is not null); + Debug.Assert(!result.Type.IsDynamic()); + result = _factory.Convert(originalAssignment.Type, result); + } + + return result; } /// /// Generates a lowered form of the assignment operator for the given left and right sub-expressions. /// Left and right sub-expressions must be in lowered form. /// - private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, TypeSymbol type, + private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, bool used, bool isChecked, bool isCompoundAssignment) { switch (rewrittenLeft.Kind) @@ -132,7 +174,7 @@ private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpressio throw ExceptionUtilities.Unreachable(); default: - return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, isRef: false, type: type, used: used); + return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, isRef: false, used: used); } } @@ -168,7 +210,6 @@ private BoundExpression MakeStaticAssignmentOperator( BoundExpression rewrittenLeft, BoundExpression rewrittenRight, bool isRef, - TypeSymbol type, bool used) { switch (rewrittenLeft.Kind) @@ -193,7 +234,6 @@ private BoundExpression MakeStaticAssignmentOperator( false, default(ImmutableArray), rewrittenRight, - type, used); } @@ -214,7 +254,6 @@ private BoundExpression MakeStaticAssignmentOperator( indexerAccess.Expanded, indexerAccess.ArgsToParamsOpt, rewrittenRight, - type, used); } @@ -227,7 +266,6 @@ private BoundExpression MakeStaticAssignmentOperator( syntax, rewrittenLeft, rewrittenRight, - type, isRef); } @@ -252,9 +290,8 @@ private BoundExpression MakeStaticAssignmentOperator( sequence.Value, rewrittenRight, isRef, - type, used), - type); + sequence.Type); } goto default; @@ -264,8 +301,7 @@ private BoundExpression MakeStaticAssignmentOperator( return _factory.AssignmentExpression( syntax, rewrittenLeft, - rewrittenRight, - type); + rewrittenRight); } } } @@ -279,7 +315,6 @@ private BoundExpression MakePropertyAssignment( bool expanded, ImmutableArray argsToParamsOpt, BoundExpression rewrittenRight, - TypeSymbol type, bool used) { // Rewrite property assignment into call to setter. @@ -350,7 +385,7 @@ private BoundExpression MakePropertyAssignment( AppendToPossibleNull(argTemps, rhsTemp), ImmutableArray.Create(setterCall), boundRhs, - type); + rhsTemp.Type); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 7e7c8d253ac70..a63910f4e0572 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -141,15 +141,13 @@ private void InterceptCallAndAdjustArguments( bool invokedAsExtensionMethod, Syntax.SimpleNameSyntax? nameSyntax) { - var interceptableLocation = nameSyntax?.Location; - if (this._compilation.TryGetInterceptor(interceptableLocation) is not var (attributeLocation, interceptor)) + if (this._compilation.TryGetInterceptor(nameSyntax) is not var (attributeLocation, interceptor)) { // The call was not intercepted. return; } Debug.Assert(nameSyntax != null); - Debug.Assert(interceptableLocation != null); Debug.Assert(interceptor.IsDefinition); Debug.Assert(!interceptor.ContainingType.IsGenericType); @@ -410,13 +408,23 @@ BoundExpression visitArgumentsAndFinishRewrite(BoundCall node, BoundExpression? Instrumenter.InterceptCallAndAdjustArguments(ref method, ref rewrittenReceiver, ref rewrittenArguments, ref argRefKindsOpt); } - var rewrittenCall = MakeCall(node, node.Syntax, rewrittenReceiver, method, rewrittenArguments, argRefKindsOpt, node.ResultKind, node.Type, temps.ToImmutableAndFree()); + var rewrittenCall = MakeCall(node, node.Syntax, rewrittenReceiver, method, rewrittenArguments, argRefKindsOpt, node.ResultKind, temps.ToImmutableAndFree()); if (Instrument) { rewrittenCall = Instrumenter.InstrumentCall(node, rewrittenCall); } + if (node.Type.IsDynamic() && !method.ReturnType.IsDynamic()) + { + Debug.Assert(node.Type.IsDynamic()); + Debug.Assert(!method.ReturnsByRef); + Debug.Assert(rewrittenCall.Type is not null); + Debug.Assert(!rewrittenCall.Type.IsDynamic()); + Debug.Assert(!rewrittenCall.Type.IsVoidType()); + rewrittenCall = _factory.Convert(node.Type, rewrittenCall); + } + return rewrittenCall; } } @@ -429,7 +437,6 @@ private BoundExpression MakeCall( ImmutableArray rewrittenArguments, ImmutableArray argumentRefKinds, LookupResultKind resultKind, - TypeSymbol type, ImmutableArray temps) { BoundExpression rewrittenBoundCall; @@ -454,7 +461,7 @@ private BoundExpression MakeCall( resultKind, rewrittenArguments[0], rewrittenArguments[1], - type); + method.ReturnType); } else if (node == null) { @@ -472,7 +479,7 @@ private BoundExpression MakeCall( argsToParamsOpt: default(ImmutableArray), defaultArguments: default(BitVector), resultKind: resultKind, - type: type); + type: method.ReturnType); } else { @@ -489,9 +496,11 @@ private BoundExpression MakeCall( argsToParamsOpt: default(ImmutableArray), defaultArguments: default(BitVector), node.ResultKind, - node.Type); + method.ReturnType); } + Debug.Assert(rewrittenBoundCall.Type is not null); + if (!temps.IsDefaultOrEmpty) { return new BoundSequence( @@ -499,13 +508,13 @@ private BoundExpression MakeCall( locals: temps, sideEffects: ImmutableArray.Empty, value: rewrittenBoundCall, - type: type); + type: rewrittenBoundCall.Type); } return rewrittenBoundCall; } - private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenReceiver, MethodSymbol method, ImmutableArray rewrittenArguments, TypeSymbol type) + private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenReceiver, MethodSymbol method, ImmutableArray rewrittenArguments) { return MakeCall( node: null, @@ -515,7 +524,6 @@ private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenRe rewrittenArguments: rewrittenArguments, argumentRefKinds: default(ImmutableArray), resultKind: LookupResultKind.Viable, - type: type, temps: default); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index ca0c4e3354ab1..ef6e145345c23 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -118,7 +118,7 @@ static BoundNode unwrapListElement(BoundCollectionExpression node, BoundNode ele if (element is BoundCollectionExpressionSpreadElement spreadElement) { Debug.Assert(spreadElement.IteratorBody is { }); - var iteratorBody = Binder.GetUnderlyingCollectionExpressionElement(node, ((BoundExpressionStatement)spreadElement.IteratorBody).Expression); + var iteratorBody = Binder.GetUnderlyingCollectionExpressionElement(node, ((BoundExpressionStatement)spreadElement.IteratorBody).Expression, throwOnErrors: true); Debug.Assert(iteratorBody is { }); return spreadElement.Update( spreadElement.Expression, @@ -131,7 +131,7 @@ static BoundNode unwrapListElement(BoundCollectionExpression node, BoundNode ele } else { - var result = Binder.GetUnderlyingCollectionExpressionElement(node, (BoundExpression)element); + var result = Binder.GetUnderlyingCollectionExpressionElement(node, (BoundExpression)element, throwOnErrors: true); Debug.Assert(result is { }); return result; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs index c015096e3b21c..ed3c55336a5d1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs @@ -113,6 +113,10 @@ private BoundExpression VisitCompoundAssignmentOperator(BoundCompoundAssignmentO temps.Free(); stores.Free(); + + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, node.Left, result, used); + + Debug.Assert(used || node.Left is not (BoundIndexerAccess { Indexer.RefKind: RefKind.None } or BoundPropertyAccess { PropertySymbol.RefKind: RefKind.None }) || result.Type?.IsVoidType() == true); return result; BoundExpression rewriteAssignment(BoundExpression leftRead) @@ -153,7 +157,8 @@ BoundExpression rewriteAssignment(BoundExpression leftRead) RemovePlaceholderReplacement(node.FinalPlaceholder); } - return MakeAssignmentOperator(syntax, transformedLHS, opFinal, node.Left.Type, used: used, isChecked: isChecked, isCompoundAssignment: true); + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.Left.Type, TypeCompareKind.AllIgnoreOptions)); + return MakeAssignmentOperator(syntax, transformedLHS, opFinal, used: used, isChecked: isChecked, isCompoundAssignment: true); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index 635043ef290bd..4f6b4f96c378d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -1421,9 +1421,6 @@ private BoundExpression RewriteIntPtrConversion( rewrittenOperand = MakeConversionNode(rewrittenOperand, method.GetParameterType(0), @checked); - var returnType = method.ReturnType; - Debug.Assert((object)returnType != null); - if (_inExpressionLambda) { return BoundConversion.Synthesized(syntax, rewrittenOperand, conversion, @checked, explicitCastInCode: explicitCastInCode, conversionGroupOpt: null, constantValueOpt, rewrittenType); @@ -1433,8 +1430,7 @@ private BoundExpression RewriteIntPtrConversion( syntax: syntax, rewrittenReceiver: null, method: method, - rewrittenArguments: ImmutableArray.Create(rewrittenOperand), - type: returnType); + rewrittenArguments: ImmutableArray.Create(rewrittenOperand)); return MakeConversionNode(rewrittenCall, rewrittenType, @checked, markAsChecked: true); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs index dfbe8f214ae22..bb24b2810febb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs @@ -18,8 +18,8 @@ internal sealed partial class LocalRewriter { var right = node.Right; Debug.Assert(right.Conversion.Kind == ConversionKind.Deconstruction); - - return RewriteDeconstruction(node.Left, right.Conversion, right.Operand, node.IsUsed); + Debug.Assert(node.Type.IsTupleType); + return RewriteDeconstruction(node.Left, right.Conversion, right.Operand, node.IsUsed, (NamedTypeSymbol)node.Type); } /// @@ -34,13 +34,12 @@ internal sealed partial class LocalRewriter /// - the conversion phase /// - the assignment phase /// - private BoundExpression? RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed) + private BoundExpression? RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed, NamedTypeSymbol assignmentResultTupleType) { var lhsTemps = ArrayBuilder.GetInstance(); var lhsEffects = ArrayBuilder.GetInstance(); ArrayBuilder lhsTargets = GetAssignmentTargetsAndSideEffects(left, lhsTemps, lhsEffects); - Debug.Assert(left.Type is { }); - BoundExpression? result = RewriteDeconstruction(lhsTargets, conversion, left.Type, right, isUsed); + BoundExpression? result = RewriteDeconstruction(lhsTargets, conversion, right, assignmentResultTupleType, isUsed); Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets); if (result is null) { @@ -55,8 +54,8 @@ internal sealed partial class LocalRewriter private BoundExpression? RewriteDeconstruction( ArrayBuilder lhsTargets, Conversion conversion, - TypeSymbol leftType, BoundExpression right, + NamedTypeSymbol assignmentResultTupleType, bool isUsed) { if (right.Kind == BoundKind.ConditionalOperator) @@ -66,17 +65,17 @@ internal sealed partial class LocalRewriter return conditional.Update( conditional.IsRef, VisitExpression(conditional.Condition), - RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Consequence, isUsed: true)!, - RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Alternative, isUsed: true)!, + RewriteDeconstruction(lhsTargets, conversion, conditional.Consequence, assignmentResultTupleType, isUsed: true)!, + RewriteDeconstruction(lhsTargets, conversion, conditional.Alternative, assignmentResultTupleType, isUsed: true)!, conditional.ConstantValueOpt, - leftType, + assignmentResultTupleType, wasTargetTyped: true, - leftType); + assignmentResultTupleType); } var temps = ArrayBuilder.GetInstance(); var effects = DeconstructionSideEffects.GetInstance(); - BoundExpression? returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, isUsed, inInit: true); + BoundExpression? returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, assignmentResultTupleType, isUsed, inInit: true); reverseAssignmentsToTargetsIfApplicable(); effects.Consolidate(); @@ -213,6 +212,7 @@ static bool canReorderTargetAssignments(ArrayBuilder temps, DeconstructionSideEffects effects, + NamedTypeSymbol assignmentResultTupleType, bool isUsed, bool inInit) { @@ -227,14 +227,19 @@ static bool canReorderTargetAssignments(ArrayBuilder(removeDelegate), - type: clearMethod.ReturnType); + rewrittenArguments: ImmutableArray.Create(removeDelegate)); } else { @@ -163,8 +163,7 @@ private BoundExpression RewriteWindowsRuntimeEventAssignmentOperator(SyntaxNode syntax: syntax, rewrittenReceiver: null, method: marshalMethod, - rewrittenArguments: marshalArguments, - type: marshalMethod.ReturnType); + rewrittenArguments: marshalArguments); } else { @@ -308,7 +307,7 @@ private BoundExpression RewriteNoPiaEventAssignmentOperator(BoundEventAssignment if ((object)addRemove != null) { - BoundExpression eventInfo = _factory.New(ctor, _factory.Typeof(node.Event.ContainingType), _factory.Literal(node.Event.MetadataName)); + BoundExpression eventInfo = _factory.New(ctor, _factory.Typeof(node.Event.ContainingType, ctor.Parameters[0].Type), _factory.Literal(node.Event.MetadataName)); result = _factory.Call(eventInfo, addRemove, _factory.Convert(addRemove.Parameters[0].Type, rewrittenReceiver), _factory.Convert(addRemove.Parameters[1].Type, rewrittenArgument)); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FunctionPointerInvocation.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FunctionPointerInvocation.cs index e0968a7089755..b98dac8930faf 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FunctionPointerInvocation.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FunctionPointerInvocation.cs @@ -35,7 +35,7 @@ internal sealed partial class LocalRewriter Debug.Assert(discardedReceiver is null); - if (node.InterceptableNameSyntax is { } nameSyntax && this._compilation.TryGetInterceptor(nameSyntax.Location) is var (attributeLocation, _)) + if (node.InterceptableNameSyntax is { } nameSyntax && this._compilation.TryGetInterceptor(nameSyntax) is var (attributeLocation, _)) { this._diagnostics.Add(ErrorCode.ERR_InterceptableMethodMustBeOrdinary, attributeLocation, nameSyntax.Identifier.ValueText); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs index 2009b7fb09125..d69554e9b960d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs @@ -91,7 +91,6 @@ private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftO node.Expanded, node.ArgsToParamsOpt, node.DefaultArguments, - node.Type, node, isLeftOfAssignment); } @@ -106,17 +105,22 @@ private BoundExpression MakeIndexerAccess( bool expanded, ImmutableArray argsToParamsOpt, BitVector defaultArguments, - TypeSymbol type, - BoundIndexerAccess? oldNodeOpt, + BoundExpression originalIndexerAccessOrObjectInitializerMember, bool isLeftOfAssignment) { + Debug.Assert(originalIndexerAccessOrObjectInitializerMember is BoundIndexerAccess or BoundObjectInitializerMember); + Debug.Assert(originalIndexerAccessOrObjectInitializerMember.Type is not null); + if (isLeftOfAssignment && indexer.RefKind == RefKind.None) { + TypeSymbol type = indexer.Type; + Debug.Assert(originalIndexerAccessOrObjectInitializerMember.Type.Equals(type, TypeCompareKind.ConsiderEverything)); + // This is an indexer set access. We return a BoundIndexerAccess node here. // This node will be rewritten with MakePropertyAssignment when rewriting the enclosing BoundAssignmentOperator. - return oldNodeOpt != null ? - oldNodeOpt.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type) : + return originalIndexerAccessOrObjectInitializerMember is BoundIndexerAccess indexerAccess ? + indexerAccess.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type) : new BoundIndexerAccess(syntax, rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type); } else @@ -145,6 +149,16 @@ private BoundExpression MakeIndexerAccess( BoundExpression call = MakePropertyGetAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, argumentRefKindsOpt, getMethod); + if (originalIndexerAccessOrObjectInitializerMember.Type.IsDynamic() == true && !indexer.Type.IsDynamic()) + { + Debug.Assert(call.Type is not null); + Debug.Assert(!call.Type.IsDynamic()); + Debug.Assert(!getMethod.ReturnsByRef); + call = _factory.Convert(originalIndexerAccessOrObjectInitializerMember.Type, call); + } + + Debug.Assert(call.Type is not null); + if (temps.Count == 0) { temps.Free(); @@ -157,7 +171,7 @@ private BoundExpression MakeIndexerAccess( temps.ToImmutableAndFree(), ImmutableArray.Empty, call, - type); + call.Type); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs index b2d42a9a8597e..7e24d7df4caa4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs @@ -63,7 +63,6 @@ internal sealed partial class LocalRewriter localSymbol.Type ), rewrittenInitializer, - localSymbol.Type, localSymbol.IsRef), hasErrors); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs index 851da6ffc4147..6b3b62aa0bc01 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs @@ -25,10 +25,12 @@ public override BoundNode VisitNullCoalescingAssignmentOperator(BoundNullCoalesc var lhsRead = MakeRValue(transformedLHS); BoundExpression loweredRight = VisitExpression(node.RightOperand); - return node.IsNullableValueTypeAssignment ? + var result = node.IsNullableValueTypeAssignment ? rewriteNullCoalescingAssignmentForValueType() : rewriteNullCoalscingAssignmentStandard(); + return ConvertResultOfAssignmentToDynamicIfNecessary(node, node.LeftOperand, result, used: true); + BoundExpression rewriteNullCoalscingAssignmentStandard() { // Now that LHS is transformed with temporaries, we rewrite this node into a coalesce expression: @@ -38,7 +40,8 @@ BoundExpression rewriteNullCoalscingAssignmentStandard() // isCompoundAssignment is only used for dynamic scenarios, and we want those scenarios to treat this like a standard assignment. // See CodeGenNullCoalescingAssignmentTests.CoalescingAssignment_DynamicRuntimeCastFailure, which will fail if // isCompoundAssignment is set to true. It will fail to throw a runtime binder cast exception. - BoundExpression assignment = MakeAssignmentOperator(syntax, transformedLHS, loweredRight, node.LeftOperand.Type, used: true, isChecked: false, isCompoundAssignment: false); + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.LeftOperand.Type, TypeCompareKind.AllIgnoreOptions)); + BoundExpression assignment = MakeAssignmentOperator(syntax, transformedLHS, loweredRight, used: true, isChecked: false, isCompoundAssignment: false); // lhsRead ?? (transformedLHS = loweredRight) var leftPlaceholder = new BoundValuePlaceholder(lhsRead.Syntax, lhsRead.Type); @@ -60,7 +63,6 @@ BoundExpression rewriteNullCoalscingAssignmentStandard() BoundExpression rewriteNullCoalescingAssignmentForValueType() { Debug.Assert(node.LeftOperand.Type.IsNullableType()); - Debug.Assert(node.Type.Equals(node.RightOperand.Type)); // We lower the expression to this form: // @@ -107,17 +109,17 @@ BoundExpression rewriteNullCoalescingAssignmentForValueType() temps.Add(tmp.LocalSymbol); // tmp = loweredRight; - var tmpAssignment = MakeAssignmentOperator(node.Syntax, tmp, loweredRight, node.Type, used: true, isChecked: false, isCompoundAssignment: false); + var tmpAssignment = MakeAssignmentOperator(node.Syntax, tmp, loweredRight, used: true, isChecked: false, isCompoundAssignment: false); Debug.Assert(transformedLHS.Type.GetNullableUnderlyingType().Equals(tmp.Type.StrippedType(), TypeCompareKind.AllIgnoreOptions)); // transformedLhs = tmp; + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.LeftOperand.Type, TypeCompareKind.AllIgnoreOptions)); var transformedLhsAssignment = MakeAssignmentOperator( node.Syntax, transformedLHS, MakeConversionNode(tmp, transformedLHS.Type, @checked: false, markAsChecked: true), - node.LeftOperand.Type, used: true, isChecked: false, isCompoundAssignment: false); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs index 5ca410e45cde8..8583c6cf6329a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs @@ -206,7 +206,7 @@ private BoundExpression MakeDynamicCollectionInitializer(BoundExpression rewritt Instrumenter.InterceptCallAndAdjustArguments(ref addMethod, ref rewrittenReceiver, ref rewrittenArguments, ref argumentRefKindsOpt); } - return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.ResultKind, addMethod.ReturnType, temps.ToImmutableAndFree()); + return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.ResultKind, temps.ToImmutableAndFree()); } private BoundExpression VisitObjectInitializerMember(BoundObjectInitializerMember node, ref BoundExpression rewrittenReceiver, ArrayBuilder sideEffects, ref ArrayBuilder? temps) @@ -363,7 +363,8 @@ private void AddObjectInitializer( { // Rewrite simple assignment to field/property. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: assignment.IsRef, assignment.Type, used: false)); + Debug.Assert(assignment.Type.IsDynamic() || TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: assignment.IsRef, used: false)); return; } } @@ -434,7 +435,8 @@ private void AddObjectInitializer( { // Rewrite simple assignment to field/property. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, used: false)); return; } @@ -466,7 +468,8 @@ private void AddObjectInitializer( { // Rewrite as simple assignment. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, used: false)); return; } @@ -499,7 +502,8 @@ private void AddObjectInitializer( if (!isRhsNestedInitializer) { var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: false, used: false)); return; } @@ -684,8 +688,7 @@ private BoundExpression MakeObjectInitializerMemberAccess( rewrittenLeft.Expanded, rewrittenLeft.ArgsToParamsOpt, rewrittenLeft.DefaultArguments, - type: propertySymbol.Type, - oldNodeOpt: null, + originalIndexerAccessOrObjectInitializerMember: rewrittenLeft, isLeftOfAssignment: !isRhsNestedInitializer); } else diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs index 5e3384761eba7..00d6f2d7854b0 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs @@ -438,7 +438,8 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) BoundExpression transformedLHS = TransformCompoundAssignmentLHS(node.Operand, isRegularCompoundAssignment: true, tempInitializers, tempSymbols, isDynamic); TypeSymbol? operandType = transformedLHS.Type; //type of the variable being incremented Debug.Assert(operandType is { }); - Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2)); + Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2) || + ShouldConvertResultOfAssignmentToDynamic(node, node.Operand)); LocalSymbol tempSymbol = _factory.SynthesizedLocal(operandType); tempSymbols.Add(tempSymbol); @@ -452,7 +453,7 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) // prefix: (X)(T.Increment((T)operand))) // postfix: (X)(T.Increment((T)temp))) - var newValue = MakeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); + var newValue = makeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); // there are two strategies for completing the rewrite. // The reason is that indirect assignments read the target of the assignment before evaluating @@ -472,122 +473,131 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) // In a case of the non-byref operand we use a single-sequence strategy as it results in shorter // overall life time of temps and as such more appropriate. (problem of crossed reads does not affect that case) // - if (IsIndirectOrInstanceField(transformedLHS)) + BoundExpression result; + + if (isIndirectOrInstanceField(transformedLHS)) { - return RewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, operandType, boundTemp, newValue); + result = rewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); } else { - return RewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, operandType, boundTemp, newValue); + result = rewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); } - } - - private static bool IsIndirectOrInstanceField(BoundExpression expression) - { - switch (expression.Kind) - { - case BoundKind.Local: - return ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None; - case BoundKind.Parameter: - Debug.Assert(!IsCapturedPrimaryConstructorParameter(expression)); - return ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None; + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, node.Operand, result, used: true); + Debug.Assert(TypeSymbol.Equals(result.Type, node.Type, TypeCompareKind.AllIgnoreOptions)); - case BoundKind.FieldAccess: - return !((BoundFieldAccess)expression).FieldSymbol.IsStatic; - } + return result; - return false; - } + static bool isIndirectOrInstanceField(BoundExpression expression) + { + switch (expression.Kind) + { + case BoundKind.Local: + return ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None; - private BoundNode RewriteWithNotRefOperand( - bool isPrefix, - bool isChecked, - ArrayBuilder tempSymbols, - ArrayBuilder tempInitializers, - SyntaxNode syntax, - BoundExpression transformedLHS, - TypeSymbol operandType, - BoundExpression boundTemp, - BoundExpression newValue) - { - // prefix: temp = (X)(T.Increment((T)operand))); operand = temp; - // postfix: temp = operand; operand = (X)(T.Increment((T)temp))); - ImmutableArray assignments = ImmutableArray.Create( - MakeAssignmentOperator(syntax, boundTemp, isPrefix ? newValue : MakeRValue(transformedLHS), operandType, used: false, isChecked: isChecked, isCompoundAssignment: false), - MakeAssignmentOperator(syntax, transformedLHS, isPrefix ? boundTemp : newValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false)); - - // prefix: Seq( operand initializers; temp = (T)(operand + 1); operand = temp; result: temp) - // postfix: Seq( operand initializers; temp = operand; operand = (T)(temp + 1); result: temp) - return new BoundSequence( - syntax: syntax, - locals: tempSymbols.ToImmutableAndFree(), - sideEffects: tempInitializers.ToImmutableAndFree().Concat(assignments), - value: boundTemp, - type: operandType); - } + case BoundKind.Parameter: + Debug.Assert(!IsCapturedPrimaryConstructorParameter(expression)); + return ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None; - private BoundNode RewriteWithRefOperand( - bool isPrefix, - bool isChecked, - ArrayBuilder tempSymbols, - ArrayBuilder tempInitializers, - SyntaxNode syntax, - BoundExpression operand, - TypeSymbol operandType, - BoundExpression boundTemp, - BoundExpression newValue) - { - var tempValue = isPrefix ? newValue : MakeRValue(operand); - Debug.Assert(tempValue.Type is { }); - var tempAssignment = MakeAssignmentOperator(syntax, boundTemp, tempValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false); + case BoundKind.FieldAccess: + return !((BoundFieldAccess)expression).FieldSymbol.IsStatic; + } - var operandValue = isPrefix ? boundTemp : newValue; - var tempAssignedAndOperandValue = new BoundSequence( - syntax, - ImmutableArray.Empty, - ImmutableArray.Create(tempAssignment), - operandValue, - tempValue.Type); - - // prefix: operand = Seq{temp = (T)(operand + 1); temp;} - // postfix: operand = Seq{temp = operand; ; (T)(temp + 1);} - BoundExpression operandAssignment = MakeAssignmentOperator(syntax, operand, tempAssignedAndOperandValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false); - - // prefix: Seq{operand initializers; operand = Seq{temp = (T)(operand + 1); temp;} result: temp} - // postfix: Seq{operand initializers; operand = Seq{temp = operand; ; (T)(temp + 1);} result: temp} - tempInitializers.Add(operandAssignment); - return new BoundSequence( - syntax: syntax, - locals: tempSymbols.ToImmutableAndFree(), - sideEffects: tempInitializers.ToImmutableAndFree(), - value: boundTemp, - type: operandType); - } + return false; + } - private BoundExpression MakeIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) - { - if (node.OperatorKind.IsDynamic()) + BoundExpression rewriteWithNotRefOperand( + bool isPrefix, + bool isChecked, + ArrayBuilder tempSymbols, + ArrayBuilder tempInitializers, + SyntaxNode syntax, + BoundExpression transformedLHS, + BoundExpression boundTemp, + BoundExpression newValue) { - return _dynamicFactory.MakeDynamicUnaryOperator(node.OperatorKind, rewrittenValueToIncrement, node.Type).ToExpression(); + Debug.Assert(boundTemp.Type is not null); + + // prefix: temp = (X)(T.Increment((T)operand))); operand = temp; + // postfix: temp = operand; operand = (X)(T.Increment((T)temp))); + ImmutableArray assignments = ImmutableArray.Create( + MakeAssignmentOperator(syntax, boundTemp, isPrefix ? newValue : MakeRValue(transformedLHS), used: false, isChecked: isChecked, isCompoundAssignment: false), + MakeAssignmentOperator(syntax, transformedLHS, isPrefix ? boundTemp : newValue, used: false, isChecked: isChecked, isCompoundAssignment: false)); + + // prefix: Seq( operand initializers; temp = (T)(operand + 1); operand = temp; result: temp) + // postfix: Seq( operand initializers; temp = operand; operand = (T)(temp + 1); result: temp) + return new BoundSequence( + syntax: syntax, + locals: tempSymbols.ToImmutableAndFree(), + sideEffects: tempInitializers.ToImmutableAndFree().Concat(assignments), + value: boundTemp, + type: boundTemp.Type); } - BoundExpression result; - if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.UserDefined) + BoundExpression rewriteWithRefOperand( + bool isPrefix, + bool isChecked, + ArrayBuilder tempSymbols, + ArrayBuilder tempInitializers, + SyntaxNode syntax, + BoundExpression operand, + BoundExpression boundTemp, + BoundExpression newValue) { - result = MakeUserDefinedIncrementOperator(node, rewrittenValueToIncrement); + Debug.Assert(boundTemp.Type is not null); + + var tempValue = isPrefix ? newValue : MakeRValue(operand); + Debug.Assert(tempValue.Type is { }); + var tempAssignment = MakeAssignmentOperator(syntax, boundTemp, tempValue, used: false, isChecked: isChecked, isCompoundAssignment: false); + + var operandValue = isPrefix ? boundTemp : newValue; + var tempAssignedAndOperandValue = new BoundSequence( + syntax, + ImmutableArray.Empty, + ImmutableArray.Create(tempAssignment), + operandValue, + tempValue.Type); + + // prefix: operand = Seq{temp = (T)(operand + 1); temp;} + // postfix: operand = Seq{temp = operand; ; (T)(temp + 1);} + BoundExpression operandAssignment = MakeAssignmentOperator(syntax, operand, tempAssignedAndOperandValue, used: false, isChecked: isChecked, isCompoundAssignment: false); + + // prefix: Seq{operand initializers; operand = Seq{temp = (T)(operand + 1); temp;} result: temp} + // postfix: Seq{operand initializers; operand = Seq{temp = operand; ; (T)(temp + 1);} result: temp} + tempInitializers.Add(operandAssignment); + return new BoundSequence( + syntax: syntax, + locals: tempSymbols.ToImmutableAndFree(), + sideEffects: tempInitializers.ToImmutableAndFree(), + value: boundTemp, + type: boundTemp.Type); } - else + + BoundExpression makeIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) { - result = MakeBuiltInIncrementOperator(node, rewrittenValueToIncrement); - } + if (node.OperatorKind.IsDynamic()) + { + return _dynamicFactory.MakeDynamicUnaryOperator(node.OperatorKind, rewrittenValueToIncrement, node.Type).ToExpression(); + } - // Generate the conversion back to the type of the original expression. + BoundExpression result; + if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.UserDefined) + { + result = MakeUserDefinedIncrementOperator(node, rewrittenValueToIncrement); + } + else + { + result = MakeBuiltInIncrementOperator(node, rewrittenValueToIncrement); + } - // (X)(short)((int)(short)x + 1) - result = ApplyConversionIfNotIdentity(node.ResultConversion, node.ResultPlaceholder, result); + // Generate the conversion back to the type of the original expression. - return result; + // (X)(short)((int)(short)x + 1) + result = ApplyConversionIfNotIdentity(node.ResultConversion, node.ResultPlaceholder, result); + + return result; + } } private BoundExpression ApplyConversionIfNotIdentity(BoundExpression? conversion, BoundValuePlaceholder? placeholder, BoundExpression replacement) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index 4997351f94f68..249c7b23c0f1e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -532,7 +532,7 @@ private BoundExpression MakeCallWithNoExplicitArgument(MethodArgumentInfo method ref temps, invokedAsExtensionMethod: method.IsExtensionMethod); - return MakeCall(null, syntax, expression, method, rewrittenArguments, argumentRefKindsOpt, LookupResultKind.Viable, method.ReturnType, temps.ToImmutableAndFree()); + return MakeCall(null, syntax, expression, method, rewrittenArguments, argumentRefKindsOpt, LookupResultKind.Viable, temps.ToImmutableAndFree()); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LoweredDynamicOperationFactory.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LoweredDynamicOperationFactory.cs index dd1ca01c49b65..0a264ac61f155 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LoweredDynamicOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LoweredDynamicOperationFactory.cs @@ -102,7 +102,7 @@ internal LoweredDynamicOperation MakeDynamicConversion( _factory.Literal((int)binderFlags), // target type: - _factory.Typeof(resultType), + _factory.Typeof(resultType, _factory.WellKnownType(WellKnownType.System_Type)), // context: _factory.TypeofDynamicOperationContextType() @@ -224,7 +224,7 @@ internal LoweredDynamicOperation MakeDynamicMemberInvocation( bool receiverIsStaticType; if (loweredReceiver.Kind == BoundKind.TypeExpression) { - loweredReceiver = _factory.Typeof(((BoundTypeExpression)loweredReceiver).Type); + loweredReceiver = _factory.Typeof(((BoundTypeExpression)loweredReceiver).Type, _factory.WellKnownType(WellKnownType.System_Type)); receiverRefKind = RefKind.None; receiverIsStaticType = true; } @@ -246,7 +246,7 @@ internal LoweredDynamicOperation MakeDynamicMemberInvocation( // type arguments: typeArgumentsWithAnnotations.IsDefaultOrEmpty ? _factory.Null(_factory.WellKnownArrayType(WellKnownType.System_Type)) : - _factory.ArrayOrEmpty(_factory.WellKnownType(WellKnownType.System_Type), _factory.TypeOfs(typeArgumentsWithAnnotations)), + _factory.ArrayOrEmpty(_factory.WellKnownType(WellKnownType.System_Type), _factory.TypeOfs(typeArgumentsWithAnnotations, _factory.WellKnownType(WellKnownType.System_Type))), // context: _factory.TypeofDynamicOperationContextType(), @@ -338,7 +338,7 @@ internal LoweredDynamicOperation MakeDynamicConstructorInvocation( { _factory.Syntax = syntax; - var loweredReceiver = _factory.Typeof(type); + var loweredReceiver = _factory.Typeof(type, _factory.WellKnownType(WellKnownType.System_Type)); MethodSymbol argumentInfoFactory = GetArgumentInfoFactory(); var binderConstruction = ((object)argumentInfoFactory != null) ? MakeBinderConstruction(WellKnownMember.Microsoft_CSharp_RuntimeBinder_Binder__InvokeConstructor, new[] diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 801b09e5d39da..ec3d3a6db7114 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -469,20 +469,20 @@ public BoundExpressionStatement ExpressionStatement(BoundExpression expr) /// public BoundExpression AssignmentExpression(BoundExpression left, BoundExpression right, bool isRef = false) { - Debug.Assert(left.Type is { } && right.Type is { } && - (left.Type.Equals(right.Type, TypeCompareKind.AllIgnoreOptions) || - StackOptimizerPass1.IsFixedBufferAssignmentToRefLocal(left, right, isRef) || - right.Type.IsErrorType() || left.Type.IsErrorType())); - - return AssignmentExpression(Syntax, left, right, left.Type, isRef: isRef, wasCompilerGenerated: true); + return AssignmentExpression(Syntax, left, right, isRef: isRef, wasCompilerGenerated: true); } /// /// Creates a general assignment that might be instrumented. /// - public BoundExpression AssignmentExpression(SyntaxNode syntax, BoundExpression left, BoundExpression right, TypeSymbol type, bool isRef = false, bool hasErrors = false, bool wasCompilerGenerated = false) + public BoundExpression AssignmentExpression(SyntaxNode syntax, BoundExpression left, BoundExpression right, bool isRef = false, bool hasErrors = false, bool wasCompilerGenerated = false) { - var assignment = new BoundAssignmentOperator(syntax, left, right, isRef, type, hasErrors) { WasCompilerGenerated = wasCompilerGenerated }; + Debug.Assert(left.Type is { } && right.Type is { } && + (left.Type.Equals(right.Type, TypeCompareKind.AllIgnoreOptions) || + StackOptimizerPass1.IsFixedBufferAssignmentToRefLocal(left, right, isRef) || + right.Type.IsErrorType() || left.Type.IsErrorType())); + + var assignment = new BoundAssignmentOperator(syntax, left, right, isRef, left.Type, hasErrors) { WasCompilerGenerated = wasCompilerGenerated }; return (InstrumentationState?.IsSuppressed == false && left is BoundLocal { LocalSymbol.SynthesizedKind: SynthesizedLocalKind.UserDefined } or BoundParameter) ? InstrumentationState.Instrumenter.InstrumentUserDefinedLocalAssignment(assignment) : @@ -859,7 +859,6 @@ public BoundExpression StaticCall(WellKnownMember method, params BoundExpression public BoundExpression StaticCall(SpecialMember method, params BoundExpression[] args) { MethodSymbol methodSymbol = SpecialMethod(method); - Binder.ReportUseSite(methodSymbol, Diagnostics, Syntax); Debug.Assert(methodSymbol.IsStatic); return Call(null, methodSymbol, args); } @@ -1283,35 +1282,51 @@ public BoundTypeExpression Type(TypeSymbol type) return new BoundTypeExpression(Syntax, null, type) { WasCompilerGenerated = true }; } - public BoundExpression Typeof(WellKnownType type) + public BoundExpression Typeof(WellKnownType type, TypeSymbol systemType) { - return Typeof(WellKnownType(type)); + return Typeof(WellKnownType(type), systemType); } - public BoundExpression Typeof(TypeSymbol type) + public BoundExpression Typeof(TypeSymbol type, TypeSymbol systemType) { + Debug.Assert(systemType.ExtendedSpecialType == InternalSpecialType.System_Type || + systemType.Equals(Compilation.GetWellKnownType(CodeAnalysis.WellKnownType.System_Type), TypeCompareKind.AllIgnoreOptions)); + + MethodSymbol getTypeFromHandle; + + if (systemType.ExtendedSpecialType == InternalSpecialType.System_Type) + { + getTypeFromHandle = SpecialMethod(CodeAnalysis.SpecialMember.System_Type__GetTypeFromHandle); + } + else + { + getTypeFromHandle = WellKnownMethod(CodeAnalysis.WellKnownMember.System_Type__GetTypeFromHandle); + } + + Debug.Assert(TypeSymbol.Equals(systemType, getTypeFromHandle.ReturnType, TypeCompareKind.AllIgnoreOptions)); + return new BoundTypeOfOperator( Syntax, Type(type), - WellKnownMethod(CodeAnalysis.WellKnownMember.System_Type__GetTypeFromHandle), - WellKnownType(CodeAnalysis.WellKnownType.System_Type)) + getTypeFromHandle, + systemType) { WasCompilerGenerated = true }; } - public BoundExpression Typeof(TypeWithAnnotations type) + public BoundExpression Typeof(TypeWithAnnotations type, TypeSymbol systemType) { - return Typeof(type.Type); + return Typeof(type.Type, systemType); } - public ImmutableArray TypeOfs(ImmutableArray typeArguments) + public ImmutableArray TypeOfs(ImmutableArray typeArguments, TypeSymbol systemType) { - return typeArguments.SelectAsArray(Typeof); + return typeArguments.SelectAsArray(Typeof, systemType); } public BoundExpression TypeofDynamicOperationContextType() { Debug.Assert(this.CompilationState is { DynamicOperationContextType: { } }); - return Typeof(this.CompilationState.DynamicOperationContextType); + return Typeof(this.CompilationState.DynamicOperationContextType, WellKnownType(CodeAnalysis.WellKnownType.System_Type)); } public BoundExpression Sizeof(TypeSymbol type) @@ -1321,11 +1336,13 @@ public BoundExpression Sizeof(TypeSymbol type) internal BoundExpression ConstructorInfo(MethodSymbol ctor) { + NamedTypeSymbol constructorInfo = WellKnownType(Microsoft.CodeAnalysis.WellKnownType.System_Reflection_ConstructorInfo); + var result = new BoundMethodInfo( Syntax, ctor, - GetMethodFromHandleMethod(ctor.ContainingType), - WellKnownType(Microsoft.CodeAnalysis.WellKnownType.System_Reflection_ConstructorInfo)) + GetMethodFromHandleMethod(ctor.ContainingType, constructorInfo), + constructorInfo) { WasCompilerGenerated = true }; #if DEBUG @@ -1417,7 +1434,7 @@ public BoundExpression SourceDocumentIndex(Cci.DebugSourceDocument document) { WasCompilerGenerated = true }; } - public BoundExpression MethodInfo(MethodSymbol method) + public BoundExpression MethodInfo(MethodSymbol method, TypeSymbol systemReflectionMethodInfo) { // The least overridden virtual method is only called for value type receivers // in special circumstances. These circumstances are exactly the checks performed by @@ -1431,8 +1448,8 @@ public BoundExpression MethodInfo(MethodSymbol method) var result = new BoundMethodInfo( Syntax, method, - GetMethodFromHandleMethod(method.ContainingType), - WellKnownType(Microsoft.CodeAnalysis.WellKnownType.System_Reflection_MethodInfo)) + GetMethodFromHandleMethod(method.ContainingType, systemReflectionMethodInfo), + systemReflectionMethodInfo) { WasCompilerGenerated = true }; #if DEBUG @@ -1452,12 +1469,28 @@ public BoundExpression FieldInfo(FieldSymbol field) { WasCompilerGenerated = true }; } - private MethodSymbol GetMethodFromHandleMethod(NamedTypeSymbol methodContainer) + private MethodSymbol GetMethodFromHandleMethod(NamedTypeSymbol methodContainer, TypeSymbol systemReflectionMethodOrConstructorInfo) { - return WellKnownMethod( - (methodContainer.AllTypeArgumentCount() == 0 && !methodContainer.IsAnonymousType) ? - CodeAnalysis.WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle : - CodeAnalysis.WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2); + Debug.Assert(systemReflectionMethodOrConstructorInfo.ExtendedSpecialType == InternalSpecialType.System_Reflection_MethodInfo || + systemReflectionMethodOrConstructorInfo.Equals(Compilation.GetWellKnownType(CodeAnalysis.WellKnownType.System_Reflection_MethodInfo), TypeCompareKind.AllIgnoreOptions) || + systemReflectionMethodOrConstructorInfo.Equals(Compilation.GetWellKnownType(CodeAnalysis.WellKnownType.System_Reflection_ConstructorInfo), TypeCompareKind.AllIgnoreOptions)); + + bool isNotInGenericType = (methodContainer.AllTypeArgumentCount() == 0 && !methodContainer.IsAnonymousType); + + if (systemReflectionMethodOrConstructorInfo.ExtendedSpecialType == InternalSpecialType.System_Reflection_MethodInfo) + { + return SpecialMethod( + isNotInGenericType ? + CodeAnalysis.SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle : + CodeAnalysis.SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle2); + } + else + { + return WellKnownMethod( + isNotInGenericType ? + CodeAnalysis.WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle : + CodeAnalysis.WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2); + } } private MethodSymbol GetFieldFromHandleMethod(NamedTypeSymbol fieldContainer) diff --git a/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj b/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj index 1611e07e1664b..fc2bc788af954 100644 --- a/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj +++ b/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj @@ -84,6 +84,7 @@ + diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index 9a43c65d82474..9c6b214de1d77 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -881,7 +881,9 @@ private IOperation CreateBoundObjectInitializerMemberOperation(BoundObjectInitia { // In nested member initializers, the property is not actually set. Instead, it is retrieved for a series of Add method calls or nested property setter calls, // so we need to use the getter for this property - MethodSymbol? accessor = isObjectOrCollectionInitializer ? property.GetOwnOrInheritedGetMethod() : property.GetOwnOrInheritedSetMethod(); + MethodSymbol? accessor = isObjectOrCollectionInitializer || property.RefKind != RefKind.None + ? property.GetOwnOrInheritedGetMethod() + : property.GetOwnOrInheritedSetMethod(); if (accessor == null || boundObjectInitializerMember.ResultKind == LookupResultKind.OverloadResolutionFailure || accessor.OriginalDefinition is ErrorMethodSymbol) { var children = CreateFromArray(((IBoundInvalidNode)boundObjectInitializerMember).InvalidNodeChildren); @@ -1257,13 +1259,14 @@ private IOperation CreateBoundCollectionExpressionElement(BoundCollectionExpress { return element is BoundCollectionExpressionSpreadElement spreadElement ? CreateBoundCollectionExpressionSpreadElement(expr, spreadElement) : - Create(Binder.GetUnderlyingCollectionExpressionElement(expr, (BoundExpression)element) ?? element); + Create(Binder.GetUnderlyingCollectionExpressionElement(expr, (BoundExpression)element, throwOnErrors: false)); } private ISpreadOperation CreateBoundCollectionExpressionSpreadElement(BoundCollectionExpression expr, BoundCollectionExpressionSpreadElement element) { - var iteratorBody = ((BoundExpressionStatement?)element.IteratorBody)?.Expression; - var iteratorItem = Binder.GetUnderlyingCollectionExpressionElement(expr, iteratorBody); + var iteratorItem = element.IteratorBody is { } iteratorBody ? + Binder.GetUnderlyingCollectionExpressionElement(expr, ((BoundExpressionStatement)iteratorBody).Expression, throwOnErrors: false) : + null; var collection = Create(element.Expression); SyntaxNode syntax = element.Syntax; bool isImplicit = element.WasCompilerGenerated; diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 4f9b6796441b9..cab4183fbee13 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -55,7 +55,11 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) return token.Kind() == SyntaxKind.EndOfFileToken || token.FullWidth != 0; } - public Cursor MoveToNextSibling() + /// + /// Returns the cursor of our next non-empty (or EOF) sibling in our parent if one exists, or `default` if + /// if doesn't. + /// + private Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() { if (this.CurrentNodeOrToken.Parent != null) { @@ -70,10 +74,6 @@ public Cursor MoveToNextSibling() return new Cursor(sibling, i); } } - - // We're at the end of this sibling chain. Walk up to the parent and see who is - // the next sibling of that. - return MoveToParent().MoveToNextSibling(); } return default(Cursor); @@ -86,6 +86,26 @@ private Cursor MoveToParent() return new Cursor(parent, index); } + public static Cursor MoveToNextSibling(Cursor cursor) + { + // Iteratively walk over the tree so that we don't stack overflow trying to recurse into anything. + while (cursor.CurrentNodeOrToken.UnderlyingNode != null) + { + var nextSibling = cursor.TryFindNextNonZeroWidthOrIsEndOfFileSibling(); + + // If we got a valid sibling, return it. + if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) + return nextSibling; + + // We're at the end of this sibling chain. Walk up to the parent and see who is + // the next sibling of that. + cursor = cursor.MoveToParent(); + } + + // Couldn't find anything, bail out. + return default; + } + private static int IndexOfNodeInParent(SyntaxNode node) { if (node.Parent == null) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index f0e6877589899..4181b444e1c75 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -118,7 +118,7 @@ private void SkipOldToken() // Now, skip past it. _changeDelta += node.FullWidth; _oldDirectives = node.ApplyDirectives(_oldDirectives); - _oldTreeCursor = _oldTreeCursor.MoveToNextSibling(); + _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); // If our cursor is now after any changes, then just skip past them while upping // the changeDelta length. This will let us know that we need to read tokens @@ -204,7 +204,7 @@ private bool TryTakeOldNodeOrToken( // We can reuse this node or token. Move us forward in the new text, and move to the // next sibling. _newPosition += currentNodeOrToken.FullWidth; - _oldTreeCursor = _oldTreeCursor.MoveToNextSibling(); + _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); _newDirectives = currentNodeOrToken.ApplyDirectives(_newDirectives); _oldDirectives = currentNodeOrToken.ApplyDirectives(_oldDirectives); diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 0d94193097554..6ee66aec39cd7 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -5801,6 +5801,7 @@ private ScanTypeFlags ScanPossibleTypeArgumentList( } ScanTypeFlags result = ScanTypeFlags.GenericTypeOrExpression; + ScanTypeFlags lastScannedType; do { @@ -5819,7 +5820,8 @@ private ScanTypeFlags ScanPossibleTypeArgumentList( return result; } - switch (this.ScanType(out _)) + lastScannedType = this.ScanType(out _); + switch (lastScannedType) { case ScanTypeFlags.NotType: greaterThanToken = null; @@ -5918,6 +5920,25 @@ private ScanTypeFlags ScanPossibleTypeArgumentList( if (this.CurrentToken.Kind != SyntaxKind.GreaterThanToken) { + // Error recovery after missing > token: + + // In the case of an identifier, we assume that there could be a missing > token + // For example, we have reached C in X token between )( + // as the user probably wants to invoke X by X<(string, string)>() + if (lastScannedType is ScanTypeFlags.TupleType && this.CurrentToken.Kind is SyntaxKind.OpenParenToken) + { + greaterThanToken = this.EatToken(SyntaxKind.GreaterThanToken); + return result; + } + greaterThanToken = null; return ScanTypeFlags.NotType; } @@ -5968,7 +5989,34 @@ private void ParseTypeArgumentList(out SyntaxToken open, SeparatedSyntaxListBuil { break; } - else if (this.CurrentToken.Kind == SyntaxKind.CommaToken || this.IsPossibleType()) + + // We prefer early terminating the argument list over parsing until exhaustion + // for better error recovery + if (tokenBreaksTypeArgumentList(this.CurrentToken)) + { + break; + } + + // We are currently past parsing a type and we encounter an unexpected identifier token + // followed by tokens that are not part of a type argument list + // Example: List<(string a, string b) Method() { } + // current token: ^^^^^^ + if (this.CurrentToken.Kind is SyntaxKind.IdentifierToken && tokenBreaksTypeArgumentList(this.PeekToken(1))) + { + break; + } + + // This is for the case where we are in a this[] accessor, and the last one of the parameters in the parameter list + // is missing a > on its type + // Example: X this[IEnumerable + // current token: ^^^^^^^^^ + if (this.CurrentToken.Kind is SyntaxKind.IdentifierToken + && this.PeekToken(1).Kind is SyntaxKind.CloseBracketToken) + { + break; + } + + if (this.CurrentToken.Kind == SyntaxKind.CommaToken || this.IsPossibleType()) { types.AddSeparator(this.EatToken(SyntaxKind.CommaToken)); types.Add(this.ParseTypeArgument()); @@ -5980,6 +6028,57 @@ private void ParseTypeArgumentList(out SyntaxToken open, SeparatedSyntaxListBuil } close = this.EatToken(SyntaxKind.GreaterThanToken); + + static bool tokenBreaksTypeArgumentList(SyntaxToken token) + { + var contextualKind = SyntaxFacts.GetContextualKeywordKind(token.ValueText); + switch (contextualKind) + { + // Example: x is IEnumerable + case SyntaxKind.OrKeyword: + // Example: x is IEnumerable + // but since we do not look as far as possible to determine whether it is + // a tuple type or an argument list, we resort to considering it as an + // argument list + case SyntaxKind.OpenParenToken: + + // Example: IEnumerable() --- (< in ) + case SyntaxKind.LessThanToken: + // Example: Method(IEnumerable null; + case SyntaxKind.EqualsGreaterThanToken: + // Example: IEnumerable list, SyntaxKind expected) diff --git a/src/Compilers/CSharp/Portable/Parser/Lexer.cs b/src/Compilers/CSharp/Portable/Parser/Lexer.cs index 9e230a8483aeb..b409d5a3abab0 100644 --- a/src/Compilers/CSharp/Portable/Parser/Lexer.cs +++ b/src/Compilers/CSharp/Portable/Parser/Lexer.cs @@ -1909,9 +1909,7 @@ private void LexSyntaxTrivia(bool afterFirstToken, bool isTrailing, ref SyntaxLi } // normal single line comment - this.ScanToEndOfLine(); - var text = TextWindow.GetText(false); - this.AddTrivia(SyntaxFactory.Comment(text), ref triviaList); + lexSingleLineComment(ref triviaList); onlyWhitespaceOnLine = false; break; } @@ -1938,12 +1936,27 @@ private void LexSyntaxTrivia(bool afterFirstToken, bool isTrailing, ref SyntaxLi // not trivia return; - case '@' when TextWindow.PeekChar(1) == '*': - // Razor comment. We pretend that it's a multi-line comment for error recovery, but it's an error case. - this.AddError(TextWindow.Position, width: 1, ErrorCode.ERR_UnexpectedCharacter, '@'); - lexMultiLineComment(ref triviaList, delimiter: '@'); - onlyWhitespaceOnLine = false; - break; + case '@': + if ((ch = TextWindow.PeekChar(1)) == '*') + { + // Razor comment. We pretend that it's a multi-line comment for error recovery, but it's an error case. + this.AddError(TextWindow.Position, width: 1, ErrorCode.ERR_UnexpectedCharacter, '@'); + lexMultiLineComment(ref triviaList, delimiter: '@'); + onlyWhitespaceOnLine = false; + break; + } + else if (ch == ':') + { + // Razor HTML transition. We pretend it's a single-line comment for error recovery. + this.AddError(TextWindow.Position, width: 1, ErrorCode.ERR_UnexpectedCharacter, '@'); + lexSingleLineComment(ref triviaList); + onlyWhitespaceOnLine = false; + break; + } + else + { + return; + } case '\r': case '\n': var endOfLine = this.ScanEndOfLine(); @@ -1992,6 +2005,13 @@ private void LexSyntaxTrivia(bool afterFirstToken, bool isTrailing, ref SyntaxLi } } + void lexSingleLineComment(ref SyntaxListBuilder triviaList) + { + this.ScanToEndOfLine(); + var text = TextWindow.GetText(false); + this.AddTrivia(SyntaxFactory.Comment(text), ref triviaList); + } + void lexMultiLineComment(ref SyntaxListBuilder triviaList, char delimiter) { bool isTerminated; diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index d3bb932bfcec2..e2eac632d971c 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -1,10 +1,28 @@ Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp12 = 1200 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion Microsoft.CodeAnalysis.CSharp.Conversion.IsCollectionExpression.get -> bool +[RSEXPERIMENTAL002]Microsoft.CodeAnalysis.CSharp.InterceptableLocation +[RSEXPERIMENTAL002]abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.Data.get -> string! +[RSEXPERIMENTAL002]abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.GetDisplayLocation() -> string! +[RSEXPERIMENTAL002]abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.Version.get -> int Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.ReadOnlyKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.WithReadOnlyKeyword(Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! +Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser +Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Dispose() -> void +Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ParseNextToken() -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result +Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ResetTo(Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result result) -> void +Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result +Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result.ContextualKind.get -> Microsoft.CodeAnalysis.CSharp.SyntaxKind +Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result.Result() -> void +Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result.Token.get -> Microsoft.CodeAnalysis.SyntaxToken +Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.SkipForwardTo(int position) -> void static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax! node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.IMethodSymbol? static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetElementConversion(this Microsoft.CodeAnalysis.Operations.ISpreadOperation! spread) -> Microsoft.CodeAnalysis.CSharp.Conversion +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CreateTokenParser(Microsoft.CodeAnalysis.Text.SourceText! sourceText, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions? options = null) -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser! static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CrefParameter(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! [RSEXPERIMENTAL001]Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetSemanticModel(Microsoft.CodeAnalysis.SyntaxTree! syntaxTree, Microsoft.CodeAnalysis.SemanticModelOptions options) -> Microsoft.CodeAnalysis.SemanticModel! +[RSEXPERIMENTAL002]override abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.Equals(object? obj) -> bool +[RSEXPERIMENTAL002]override abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.GetHashCode() -> int [RSEXPERIMENTAL002]static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptorMethod(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax! node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.IMethodSymbol? +[RSEXPERIMENTAL002]static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptableLocation(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax! node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.CSharp.InterceptableLocation? +[RSEXPERIMENTAL002]static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptsLocationAttributeSyntax(this Microsoft.CodeAnalysis.CSharp.InterceptableLocation! location) -> string! diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs index d2705053fda04..913808058a9d8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs @@ -33,9 +33,9 @@ public NamedTypeSymbol ConstructAnonymousTypeSymbol(AnonymousTypeDescriptor type return new AnonymousTypePublicSymbol(this, typeDescr); } - public NamedTypeSymbol ConstructAnonymousDelegateSymbol(AnonymousTypeDescriptor typeDescr, bool checkParamsCollectionsFeatureAvailability) + public NamedTypeSymbol ConstructAnonymousDelegateSymbol(AnonymousTypeDescriptor typeDescr) { - return new AnonymousDelegatePublicSymbol(this, typeDescr, checkParamsCollectionsFeatureAvailability); + return new AnonymousDelegatePublicSymbol(this, typeDescr); } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.DelegatePublicSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.DelegatePublicSymbol.cs index 3c28925b21379..3f1246a22190f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.DelegatePublicSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.DelegatePublicSymbol.cs @@ -16,20 +16,9 @@ internal sealed class AnonymousDelegatePublicSymbol : AnonymousTypeOrDelegatePub { private ImmutableArray _lazyMembers; - /// - /// This member does not participate in equality because it is not reflecting any semantic aspect of the symbol. - /// It is only used to determine if we need to check for - /// feature availability, which happens if this field is set to 'true'. - /// If in the process of merging equivalent types, the one with 'false' wins over the one with 'true', - /// that is fine, because that means that the feature availability check is performed on a - /// method declared in this compilation. - /// - internal readonly bool CheckParamsCollectionsFeatureAvailability; - - internal AnonymousDelegatePublicSymbol(AnonymousTypeManager manager, AnonymousTypeDescriptor typeDescr, bool checkParamsCollectionsFeatureAvailability) : + internal AnonymousDelegatePublicSymbol(AnonymousTypeManager manager, AnonymousTypeDescriptor typeDescr) : base(manager, typeDescr) { - CheckParamsCollectionsFeatureAvailability = checkParamsCollectionsFeatureAvailability; } internal override NamedTypeSymbol MapToImplementationSymbol() @@ -41,7 +30,7 @@ internal override AnonymousTypeOrDelegatePublicSymbol SubstituteTypes(AbstractTy { var typeDescr = TypeDescriptor.SubstituteTypes(map, out bool changed); return changed ? - new AnonymousDelegatePublicSymbol(Manager, typeDescr, CheckParamsCollectionsFeatureAvailability) : + new AnonymousDelegatePublicSymbol(Manager, typeDescr) : this; } diff --git a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs index 4d7e123e1468d..d5f5bcbe61e54 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs @@ -109,10 +109,12 @@ private ReducedExtensionMethodSymbol(MethodSymbol reducedFrom) /// are not satisfied, the return value is null. /// /// Compilation used to check constraints. The latest language version is assumed if this is null. - private static MethodSymbol InferExtensionMethodTypeArguments(MethodSymbol method, TypeSymbol thisType, CSharpCompilation compilation, + internal static MethodSymbol InferExtensionMethodTypeArguments(MethodSymbol method, TypeSymbol thisType, CSharpCompilation compilation, ref CompoundUseSiteInfo useSiteInfo, out bool wasFullyInferred) { Debug.Assert(method.IsExtensionMethod); + Debug.Assert(method.MethodKind != MethodKind.ReducedExtension); + Debug.Assert(method.ParameterCount > 0); Debug.Assert((object)thisType != null); if (!method.IsGenericMethod || method != method.ConstructedFrom) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 85eba6bdf14bb..27167398d80fe 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -1579,7 +1579,7 @@ void validateParamsType(BindingDiagnosticBag diagnostics) checkIsAtLeastAsVisible(syntax, binder, constructor, diagnostics); } - if (!binder.HasCollectionExpressionApplicableAddMethod(syntax, Type, elementType, out ImmutableArray addMethods, diagnostics)) + if (!binder.HasCollectionExpressionApplicableAddMethod(syntax, Type, out ImmutableArray addMethods, diagnostics)) { return; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index c23defaca34cc..70bbd0a42458f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -5,11 +5,13 @@ #nullable disable using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -942,21 +944,186 @@ private void DecodeModuleInitializerAttribute(DecodeWellKnownAttributeArguments< private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments arguments) { - Debug.Assert(arguments.AttributeSyntaxOpt is object); Debug.Assert(!arguments.Attribute.HasErrors); - var attributeData = arguments.Attribute; - var attributeArguments = attributeData.CommonConstructorArguments; - if (attributeArguments is not [ + var constructorArguments = arguments.Attribute.CommonConstructorArguments; + if (constructorArguments is [ { Type.SpecialType: SpecialType.System_String }, { Kind: not TypedConstantKind.Array, Value: int lineNumberOneBased }, { Kind: not TypedConstantKind.Array, Value: int characterNumberOneBased }]) { - // Since the attribute does not have errors (asserted above), it should be guaranteed that we have the above arguments. - throw ExceptionUtilities.Unreachable(); + DecodeInterceptsLocationAttributeExperimentalCompat(arguments, attributeFilePath: (string?)constructorArguments[0].Value, lineNumberOneBased, characterNumberOneBased); + } + else + { + Debug.Assert(arguments.Attribute.AttributeConstructor.Parameters is [{ Type.SpecialType: SpecialType.System_Int32 }, { Type.SpecialType: SpecialType.System_String }]); + DecodeInterceptsLocationChecksumBased(arguments, version: (int)constructorArguments[0].Value!, data: (string?)constructorArguments[1].Value); } + } + private void DecodeInterceptsLocationChecksumBased(DecodeWellKnownAttributeArguments arguments, int version, string? data) + { + var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; + Debug.Assert(arguments.AttributeSyntaxOpt is not null); + var attributeNameSyntax = arguments.AttributeSyntaxOpt.Name; // used for reporting diagnostics + var attributeLocation = attributeNameSyntax.Location; + + if (version != 1) + { + diagnostics.Add(ErrorCode.ERR_InterceptsLocationUnsupportedVersion, attributeLocation, version); + return; + } + + if (InterceptableLocation1.Decode(data) is not var (hash, position, displayFileName)) + { + diagnostics.Add(ErrorCode.ERR_InterceptsLocationDataInvalidFormat, attributeLocation); + return; + } + + var interceptorsNamespaces = ((CSharpParseOptions)attributeNameSyntax.SyntaxTree.Options).InterceptorsPreviewNamespaces; + var thisNamespaceNames = getNamespaceNames(this); + var foundAnyMatch = interceptorsNamespaces.Any(static (ns, thisNamespaceNames) => isDeclaredInNamespace(thisNamespaceNames, ns), thisNamespaceNames); + if (!foundAnyMatch) + { + reportFeatureNotEnabled(diagnostics, attributeLocation, thisNamespaceNames); + thisNamespaceNames.Free(); + return; + } + thisNamespaceNames.Free(); + + if (ContainingType.IsGenericType) + { + diagnostics.Add(ErrorCode.ERR_InterceptorContainingTypeCannotBeGeneric, attributeLocation, this); + return; + } + + if (MethodKind != MethodKind.Ordinary) + { + diagnostics.Add(ErrorCode.ERR_InterceptorMethodMustBeOrdinary, attributeLocation); + return; + } + + Debug.Assert(_lazyCustomAttributesBag.IsEarlyDecodedWellKnownAttributeDataComputed); + var unmanagedCallersOnly = this.GetUnmanagedCallersOnlyAttributeData(forceComplete: false); + if (unmanagedCallersOnly != null) + { + diagnostics.Add(ErrorCode.ERR_InterceptorCannotUseUnmanagedCallersOnly, attributeLocation); + return; + } + + var matchingTrees = DeclaringCompilation.GetSyntaxTreesByContentHash(hash); + if (matchingTrees.Count > 1) + { + diagnostics.Add(ErrorCode.ERR_InterceptsLocationDuplicateFile, attributeLocation, displayFileName); + return; + } + + if (matchingTrees.Count == 0) + { + diagnostics.Add(ErrorCode.ERR_InterceptsLocationFileNotFound, attributeLocation, displayFileName); + return; + } + + Debug.Assert(matchingTrees.Count == 1); + SyntaxTree? matchingTree = matchingTrees[0]; + + var root = matchingTree.GetRoot(); + if (position < 0 || position > root.EndPosition) + { + diagnostics.Add(ErrorCode.ERR_InterceptsLocationDataInvalidPosition, attributeLocation, displayFileName); + return; + } + + var referencedLines = matchingTree.GetText().Lines; + var referencedLineCount = referencedLines.Count; + var referencedToken = root.FindToken(position); + switch (referencedToken) + { + case { Parent: SimpleNameSyntax { Parent: MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax } memberAccess } rhs } when memberAccess.Name == rhs: + case { Parent: SimpleNameSyntax { Parent: MemberBindingExpressionSyntax { Parent: InvocationExpressionSyntax } memberBinding } rhs1 } when memberBinding.Name == rhs1: + case { Parent: SimpleNameSyntax { Parent: InvocationExpressionSyntax invocation } simpleName } when invocation.Expression == simpleName: + // happy case + break; + case { Parent: SimpleNameSyntax { Parent: not (MemberAccessExpressionSyntax or MemberBindingExpressionSyntax) } }: + case { Parent: SimpleNameSyntax { Parent: MemberAccessExpressionSyntax memberAccess } rhs } when memberAccess.Name == rhs: + case { Parent: SimpleNameSyntax { Parent: MemberBindingExpressionSyntax memberBinding } rhs1 } when memberBinding.Name == rhs1: + // NB: there are all sorts of places "simple names" can appear in syntax. With these checks we are trying to + // minimize confusion about why the name being used is not *interceptable*, but it's done on a best-effort basis. + + diagnostics.Add(ErrorCode.ERR_InterceptorNameNotInvoked, attributeLocation, referencedToken.Text); + return; + default: + diagnostics.Add(ErrorCode.ERR_InterceptorPositionBadToken, attributeLocation, referencedToken.Text); + return; + } + + if (position != referencedToken.Position) + { + diagnostics.Add(ErrorCode.ERR_InterceptsLocationDataInvalidPosition, attributeLocation, displayFileName); + return; + } + + DeclaringCompilation.AddInterception(matchingTree.FilePath, position, attributeLocation, this); + + // Caller must free the returned builder. + static ArrayBuilder getNamespaceNames(SourceMethodSymbolWithAttributes @this) + { + var namespaceNames = ArrayBuilder.GetInstance(); + for (var containingNamespace = @this.ContainingNamespace; containingNamespace?.IsGlobalNamespace == false; containingNamespace = containingNamespace.ContainingNamespace) + namespaceNames.Add(containingNamespace.Name); + // order outermost->innermost + // e.g. for method MyApp.Generated.Interceptors.MyInterceptor(): ["MyApp", "Generated", "Interceptors"] + namespaceNames.ReverseContents(); + return namespaceNames; + } + + static bool isDeclaredInNamespace(ArrayBuilder thisNamespaceNames, ImmutableArray namespaceSegments) + { + Debug.Assert(namespaceSegments.Length > 0); + if (namespaceSegments is ["global"]) + { + return true; + } + + if (namespaceSegments.Length > thisNamespaceNames.Count) + { + // the enabled NS has more components than interceptor's NS, so it will never match. + return false; + } + + for (var i = 0; i < namespaceSegments.Length; i++) + { + if (namespaceSegments[i] != thisNamespaceNames[i]) + { + return false; + } + } + return true; + } + + static void reportFeatureNotEnabled(BindingDiagnosticBag diagnostics, Location attributeLocation, ArrayBuilder namespaceNames) + { + if (namespaceNames.Count == 0) + { + diagnostics.Add(ErrorCode.ERR_InterceptorGlobalNamespace, attributeLocation); + } + else + { + var recommendedProperty = $"$(InterceptorsPreviewNamespaces);{string.Join(".", namespaceNames)}"; + diagnostics.Add(ErrorCode.ERR_InterceptorsFeatureNotEnabled, attributeLocation, recommendedProperty); + } + } + } + + // https://github.com/dotnet/roslyn/issues/72265: Remove support for path-based interceptors prior to stable release. + private void DecodeInterceptsLocationAttributeExperimentalCompat( + DecodeWellKnownAttributeArguments arguments, + string? attributeFilePath, + int lineNumberOneBased, + int characterNumberOneBased) + { var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; var attributeSyntax = arguments.AttributeSyntaxOpt; + Debug.Assert(attributeSyntax is object); var attributeLocation = attributeSyntax.Location; const int filePathParameterIndex = 0; const int lineNumberParameterIndex = 1; @@ -973,7 +1140,7 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments } thisNamespaceNames.Free(); - var attributeFilePath = (string?)attributeArguments[0].Value; + var attributeData = arguments.Attribute; if (attributeFilePath is null) { diagnostics.Add(ErrorCode.ERR_InterceptorFilePathCannotBeNull, attributeData.GetAttributeArgumentLocation(filePathParameterIndex)); @@ -1100,14 +1267,15 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments } // Did they actually refer to the start of the token, not the middle, or in trivia? - if (referencedPosition != referencedToken.Span.Start) + // NB: here we don't want the provided position to refer to the start of token's leading trivia, in the checksum-based way we *do* want it to refer to the start of leading trivia (i.e. the Position) + if (referencedPosition != referencedToken.SpanStart) { var linePositionZeroBased = referencedToken.GetLocation().GetLineSpan().StartLinePosition; diagnostics.Add(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, attributeLocation, referencedToken.Text, linePositionZeroBased.Line + 1, linePositionZeroBased.Character + 1); return; } - DeclaringCompilation.AddInterception(matchingTree.FilePath, lineNumberZeroBased, characterNumberZeroBased, attributeLocation, this); + DeclaringCompilation.AddInterception(matchingTree.FilePath, referencedToken.Position, attributeLocation, this); // Caller must free the returned builder. ArrayBuilder getNamespaceNames() diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs index 0ac9cd56a77fe..c64029e821ca8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs @@ -170,7 +170,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, try { F.CurrentFunction = this; - F.CloseMethod(F.Block(F.Return(F.Typeof(ContainingType)))); + F.CloseMethod(F.Block(F.Return(F.Typeof(ContainingType, ReturnType)))); } catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex) { diff --git a/src/Compilers/CSharp/Portable/Syntax/SourceTextTokenParser.cs b/src/Compilers/CSharp/Portable/Syntax/SourceTextTokenParser.cs new file mode 100644 index 0000000000000..8c701e1797c1d --- /dev/null +++ b/src/Compilers/CSharp/Portable/Syntax/SourceTextTokenParser.cs @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more 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 InternalSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax; + +namespace Microsoft.CodeAnalysis.CSharp; + +/// +/// A token parser that can be used to parse tokens continuously from a source. This parser parses continuously; every call to +/// will return the next token in the source text, starting from position 0. +/// can be used to skip forward in the file to a specific position, and can be used to reset the parser +/// to a previously-lexed position. +/// +/// +/// This type is safe to double dispose, but it is not safe to use after it has been disposed. Behavior in such scenarios +/// is undefined. +/// +/// This type is not thread safe. +/// +public sealed class SyntaxTokenParser : IDisposable +{ + private InternalSyntax.Lexer _lexer; + + internal SyntaxTokenParser(InternalSyntax.Lexer lexer) + { + _lexer = lexer; + } + + public void Dispose() + { + var lexer = Interlocked.CompareExchange(ref _lexer!, null, _lexer); + lexer?.Dispose(); + } + + /// + /// Parse the next token from the input at the current position. This will advance the internal position of the token parser to the + /// end of the returned token, including any trailing trivia. + /// + /// + /// The returned token will have a parent of . + /// + /// Since this API does not create a that owns all produced tokens, + /// the API may yield surprising results for + /// the produced tokens and its behavior is generally unspecified. + /// + public Result ParseNextToken() + { + var startingDirectiveStack = _lexer.Directives; + var startingPosition = _lexer.TextWindow.Position; + var token = _lexer.Lex(InternalSyntax.LexerMode.Syntax); + return new Result(new SyntaxToken(parent: null, token, startingPosition, index: 0), startingDirectiveStack); + } + + /// + /// Skip forward in the input to the specified position. Current directive state is preserved during the skip. + /// + /// The absolute location in the original text to move to. + /// If the given position is less than the current position of the lexer. + public void SkipForwardTo(int position) + { + if (position < _lexer.TextWindow.Position) + throw new ArgumentOutOfRangeException(nameof(position)); + + _lexer.TextWindow.Reset(position); + } + + /// + /// Resets the token parser to an earlier position in the input. The parser is reset to the start of the token that was previously + /// parsed, before any leading trivia, with the directive state that existed at the start of the token. + /// + public void ResetTo(Result result) + { + _lexer.Reset(result.Token.Position, result.ContextStartDirectiveStack); + } + + /// + /// The result of a call to . This is also a context object that can be used to reset the parser to + /// before the token it represents was parsed. + /// + /// + /// This type is not default safe. Attempts to use default(Result) will result in undefined behavior. + /// + public readonly struct Result + { + /// + /// The token that was parsed. + /// + public readonly SyntaxToken Token { get; } + + /// + /// If the parsed token is potentially a contextual keyword, this will return the contextual kind of the token. Otherwise, it + /// will return . + /// + public readonly SyntaxKind ContextualKind + { + get + { + var contextualKind = Token.ContextualKind(); + return contextualKind == Token.Kind() ? SyntaxKind.None : contextualKind; + } + } + + internal readonly InternalSyntax.DirectiveStack ContextStartDirectiveStack; + + internal Result(SyntaxToken token, InternalSyntax.DirectiveStack contextStartDirectiveStack) + { + Token = token; + ContextStartDirectiveStack = contextStartDirectiveStack; + } + } +} diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs index 6484b75a769c9..cba16e079df44 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.CSharp.Syntax { @@ -100,130 +101,147 @@ private static bool AreTokensEquivalent(GreenNode? before, GreenNode? after, Fun private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, Func? ignoreChildNode, bool topLevel) { - if (before == after) - { - return true; - } + // Use an explicit stack so we can walk down deep trees without blowing the real stack. + var stack = ArrayBuilder<(GreenNode? before, GreenNode? after)>.GetInstance(); + stack.Push((before, after)); - if (before == null || after == null) + try { - return false; - } + while (stack.TryPop(out var current)) + { + if (!areEquivalentSingleLevel(current.before, current.after)) + return false; + } - if (before.RawKind != after.RawKind) - { - return false; + return true; } - - if (before.IsToken) + finally { - Debug.Assert(after.IsToken); - return AreTokensEquivalent(before, after, ignoreChildNode); + stack.Free(); } - if (topLevel) + bool areEquivalentSingleLevel(GreenNode? before, GreenNode? after) { - // Once we get down to the body level we don't need to go any further and we can - // consider these trees equivalent. - switch ((SyntaxKind)before.RawKind) + if (before == after) { - case SyntaxKind.Block: - case SyntaxKind.ArrowExpressionClause: - return AreNullableDirectivesEquivalent(before, after, ignoreChildNode); + return true; } - // If we're only checking top level equivalence, then we don't have to go down into - // the initializer for a field. However, we can't put that optimization for all - // fields. For example, fields that are 'const' do need their initializers checked as - // changing them can affect binding results. - if ((SyntaxKind)before.RawKind == SyntaxKind.FieldDeclaration) + if (before == null || after == null) { - var fieldBefore = (Green.FieldDeclarationSyntax)before; - var fieldAfter = (Green.FieldDeclarationSyntax)after; - - var isConstBefore = fieldBefore.Modifiers.Any((int)SyntaxKind.ConstKeyword); - var isConstAfter = fieldAfter.Modifiers.Any((int)SyntaxKind.ConstKeyword); + return false; + } - if (!isConstBefore && !isConstAfter) - { - ignoreChildNode = childKind => childKind == SyntaxKind.EqualsValueClause; - } + if (before.RawKind != after.RawKind) + { + return false; } - // NOTE(cyrusn): Do we want to avoid going down into attribute expressions? I don't - // think we can avoid it as there are likely places in the compiler that use these - // expressions. For example, if the user changes [InternalsVisibleTo("goo")] to - // [InternalsVisibleTo("bar")] then that must count as a top level change as it - // affects symbol visibility. Perhaps we could enumerate the places in the compiler - // that use the values inside source attributes and we can check if we're in an - // attribute with that name. It wouldn't be 100% correct (because of annoying things - // like using aliases), but would likely be good enough for the incremental cases in - // the IDE. - } + if (before.IsToken) + { + Debug.Assert(after.IsToken); + return AreTokensEquivalent(before, after, ignoreChildNode); + } - if (ignoreChildNode != null) - { - var e1 = before.ChildNodesAndTokens().GetEnumerator(); - var e2 = after.ChildNodesAndTokens().GetEnumerator(); - while (true) + if (topLevel) { - GreenNode? child1 = null; - GreenNode? child2 = null; + // Once we get down to the body level we don't need to go any further and we can + // consider these trees equivalent. + switch ((SyntaxKind)before.RawKind) + { + case SyntaxKind.Block: + case SyntaxKind.ArrowExpressionClause: + return AreNullableDirectivesEquivalent(before, after, ignoreChildNode); + } - // skip ignored children: - while (e1.MoveNext()) + // If we're only checking top level equivalence, then we don't have to go down into + // the initializer for a field. However, we can't put that optimization for all + // fields. For example, fields that are 'const' do need their initializers checked as + // changing them can affect binding results. + if ((SyntaxKind)before.RawKind == SyntaxKind.FieldDeclaration) { - var c = e1.Current; - if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + var fieldBefore = (Green.FieldDeclarationSyntax)before; + var fieldAfter = (Green.FieldDeclarationSyntax)after; + + var isConstBefore = fieldBefore.Modifiers.Any((int)SyntaxKind.ConstKeyword); + var isConstAfter = fieldAfter.Modifiers.Any((int)SyntaxKind.ConstKeyword); + + if (!isConstBefore && !isConstAfter) { - child1 = c; - break; + ignoreChildNode = static childKind => childKind == SyntaxKind.EqualsValueClause; } } - while (e2.MoveNext()) + // NOTE(cyrusn): Do we want to avoid going down into attribute expressions? I don't + // think we can avoid it as there are likely places in the compiler that use these + // expressions. For example, if the user changes [InternalsVisibleTo("goo")] to + // [InternalsVisibleTo("bar")] then that must count as a top level change as it + // affects symbol visibility. Perhaps we could enumerate the places in the compiler + // that use the values inside source attributes and we can check if we're in an + // attribute with that name. It wouldn't be 100% correct (because of annoying things + // like using aliases), but would likely be good enough for the incremental cases in + // the IDE. + } + + if (ignoreChildNode != null) + { + var e1 = before.ChildNodesAndTokens().GetEnumerator(); + var e2 = after.ChildNodesAndTokens().GetEnumerator(); + while (true) { - var c = e2.Current; - if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + GreenNode? child1 = null; + GreenNode? child2 = null; + + // skip ignored children: + while (e1.MoveNext()) { - child2 = c; - break; + var c = e1.Current; + if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + { + child1 = c; + break; + } } - } - if (child1 == null || child2 == null) - { - // false if some children remained - return child1 == child2; - } + while (e2.MoveNext()) + { + var c = e2.Current; + if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + { + child2 = c; + break; + } + } - if (!AreEquivalentRecursive(child1, child2, ignoreChildNode, topLevel)) - { - return false; - } - } - } - else - { - // simple comparison - not ignoring children + if (child1 == null || child2 == null) + { + // false if some children remained + return child1 == child2; + } - int slotCount = before.SlotCount; - if (slotCount != after.SlotCount) - { - return false; + stack.Push((child1, child2)); + } } - - for (int i = 0; i < slotCount; i++) + else { - var child1 = before.GetSlot(i); - var child2 = after.GetSlot(i); + // simple comparison - not ignoring children - if (!AreEquivalentRecursive(child1, child2, ignoreChildNode, topLevel)) + int slotCount = before.SlotCount; + if (slotCount != after.SlotCount) { return false; } + + // Walk the children backwards so that we can push them onto the stack and continue walking in DFS order. + for (var i = slotCount - 1; i >= 0; i--) + { + var child1 = before.GetSlot(i); + var child2 = after.GetSlot(i); + stack.Push((child1, child2)); + } } + // So far these are equivalent. Continue checking the children. return true; } } diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs index ab864b3398b07..c8d16b0633525 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs @@ -1672,6 +1672,16 @@ public static IEnumerable ParseTokens(string text, int offset = 0, } } + /// + /// Creates a token parser that can be used to parse tokens from a given source text. + /// + /// The source to parse tokens from. + /// Parse options for the source. + public static SyntaxTokenParser CreateTokenParser(SourceText sourceText, CSharpParseOptions? options = null) + { + return new SyntaxTokenParser(new InternalSyntax.Lexer(sourceText, options ?? CSharpParseOptions.Default)); + } + /// /// Parse a NameSyntax node using the grammar rule for names. /// diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs index ba9c1c76ae68f..69c3de99c3b5a 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs @@ -77,6 +77,9 @@ public enum SyntaxKind : ushort /// Represents .. token. DotDotToken = 8222, + // Values ranging from 8193 (TildeToken) to 8287 (GreaterThanGreaterThanGreaterThanEqualsToken) are reserved for punctuation kinds. + // This gap is included within that range. So if you add a value here make sure `SyntaxFacts.GetPunctuationKinds` includes it in the returned enumeration + // additional xml tokens /// Represents /> token. SlashGreaterThanToken = 8232, // xml empty element end @@ -95,6 +98,9 @@ public enum SyntaxKind : ushort /// Represents ?> token. XmlProcessingInstructionEndToken = 8239, // ?> + // Values ranging from 8193 (TildeToken) to 8287 (GreaterThanGreaterThanGreaterThanEqualsToken) are reserved for punctuation kinds. + // This gap is included within that range. So if you add a value here make sure `SyntaxFacts.GetPunctuationKinds` includes it in the returned enumeration + // compound punctuation /// Represents || token. BarBarToken = 8260, diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs index 652092a89b94f..6a19e29e2e469 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.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; using System.Collections.Generic; +using System.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp { @@ -17,6 +19,7 @@ public static IEnumerable GetReservedKeywordKinds() { for (int i = (int)SyntaxKind.BoolKeyword; i <= (int)SyntaxKind.ImplicitKeyword; i++) { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); yield return (SyntaxKind)i; } } @@ -142,9 +145,10 @@ public static IEnumerable GetPreprocessorKeywordKinds() yield return SyntaxKind.TrueKeyword; yield return SyntaxKind.FalseKeyword; yield return SyntaxKind.DefaultKeyword; - yield return SyntaxKind.HiddenKeyword; + for (int i = (int)SyntaxKind.ElifKeyword; i <= (int)SyntaxKind.RestoreKeyword; i++) { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); yield return (SyntaxKind)i; } } @@ -172,10 +176,26 @@ private static bool IsDebuggerSpecialPunctuation(SyntaxKind kind) public static IEnumerable GetPunctuationKinds() { - for (int i = (int)SyntaxKind.TildeToken; i <= (int)SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; i++) + for (int i = (int)SyntaxKind.TildeToken; i <= (int)SyntaxKind.DotDotToken; i++) + { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); + yield return (SyntaxKind)i; + } + + for (int i = (int)SyntaxKind.SlashGreaterThanToken; i <= (int)SyntaxKind.XmlProcessingInstructionEndToken; i++) { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); yield return (SyntaxKind)i; } + + for (int i = (int)SyntaxKind.BarBarToken; i <= (int)SyntaxKind.QuestionQuestionEqualsToken; i++) + { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); + yield return (SyntaxKind)i; + } + + yield return SyntaxKind.GreaterThanGreaterThanGreaterThanToken; + yield return SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; } public static bool IsPunctuationOrKeyword(SyntaxKind kind) @@ -1148,7 +1168,12 @@ public static IEnumerable GetContextualKeywordKinds() { for (int i = (int)SyntaxKind.YieldKeyword; i <= (int)SyntaxKind.FileKeyword; i++) { - yield return (SyntaxKind)i; + // 8441 corresponds to a deleted kind (DataKeyword) that was previously shipped. + if (i != 8441) + { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); + yield return (SyntaxKind)i; + } } } diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs index 7d65e42cb3279..6e2280f48fead 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs @@ -372,6 +372,7 @@ private static bool IsDeconstructionCompatibleArgument(ExpressionSyntax expressi return invocation.Expression switch { MemberAccessExpressionSyntax memberAccess => memberAccess.Name, + MemberBindingExpressionSyntax memberBinding => memberBinding.Name, SimpleNameSyntax name => name, _ => null }; diff --git a/src/Compilers/CSharp/Portable/Utilities/ContentHashComparer.cs b/src/Compilers/CSharp/Portable/Utilities/ContentHashComparer.cs new file mode 100644 index 0000000000000..4bc6b50362f96 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Utilities/ContentHashComparer.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.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp; + +internal sealed class ContentHashComparer : IEqualityComparer> +{ + public static ContentHashComparer Instance { get; } = new ContentHashComparer(); + + private ContentHashComparer() { } + + public bool Equals(ReadOnlyMemory x, ReadOnlyMemory y) + { + return x.Span.SequenceEqual(y.Span); + } + + public int GetHashCode(ReadOnlyMemory obj) + { + // We expect the content hash to be well-mixed. + // Therefore simply reading the first 4 bytes of it results in an adequate hash code. + return BinaryPrimitives.ReadInt32LittleEndian(obj.Span); + } +} diff --git a/src/Compilers/CSharp/Portable/Utilities/InterceptableLocation.cs b/src/Compilers/CSharp/Portable/Utilities/InterceptableLocation.cs new file mode 100644 index 0000000000000..4f3a23634d307 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Utilities/InterceptableLocation.cs @@ -0,0 +1,180 @@ +// Licensed to the .NET Foundation under one or more 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.Buffers.Binary; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Cci; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp; + +[Experimental(RoslynExperiments.Interceptors, UrlFormat = RoslynExperiments.Interceptors_Url)] +public abstract class InterceptableLocation +{ + private protected InterceptableLocation() { } + + /// + /// The version of the location encoding. Used as an argument to 'InterceptsLocationAttribute'. + /// + public abstract int Version { get; } + + /// + /// Opaque data which references a call when used as an argument to 'InterceptsLocationAttribute'. + /// The value does not require escaping, i.e. it is valid in a string literal when wrapped in " (double-quote) characters. + /// + public abstract string Data { get; } + + /// + /// Gets a human-readable representation of the location, suitable for including in comments in generated code. + /// + public abstract string GetDisplayLocation(); + + public abstract override bool Equals(object? obj); + public abstract override int GetHashCode(); +} + +#pragma warning disable RSEXPERIMENTAL002 // internal usage of experimental API +/// +/// Version 1 of the InterceptableLocation encoding. +/// +internal sealed class InterceptableLocation1 : InterceptableLocation +{ + internal const int ContentHashLength = 16; + private static readonly UTF8Encoding s_encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + + private readonly ImmutableArray _checksum; + private readonly string _path; + private readonly int _position; + private readonly int _lineNumberOneIndexed; + private readonly int _characterNumberOneIndexed; + private string? _lazyData; + + internal InterceptableLocation1(ImmutableArray checksum, string path, int position, int lineNumberOneIndexed, int characterNumberOneIndexed) + { + Debug.Assert(checksum.Length == ContentHashLength); + Debug.Assert(path is not null); + Debug.Assert(position >= 0); + Debug.Assert(lineNumberOneIndexed > 0); + Debug.Assert(characterNumberOneIndexed > 0); + + _checksum = checksum; + _path = path; + _position = position; + _lineNumberOneIndexed = lineNumberOneIndexed; + _characterNumberOneIndexed = characterNumberOneIndexed; + } + + public override string GetDisplayLocation() + { + // e.g. `C:\project\src\Program.cs(12,34)` + return $"{_path}({_lineNumberOneIndexed},{_characterNumberOneIndexed})"; + } + + public override string ToString() => GetDisplayLocation(); + + public override int Version => 1; + public override string Data + { + get + { + if (_lazyData is null) + _lazyData = makeData(); + + return _lazyData; + + string makeData() + { + var builder = PooledBlobBuilder.GetInstance(); + builder.WriteBytes(_checksum, start: 0, 16); + builder.WriteInt32(_position); + + var displayFileName = Path.GetFileName(_path); + builder.WriteUTF8(displayFileName); + + var bytes = builder.ToArray(); + builder.Free(); + return Convert.ToBase64String(bytes); + } + } + } + + internal static (ReadOnlyMemory checksum, int position, string displayFileName)? Decode(string? data) + { + if (data is null) + { + return null; + } + + byte[] bytes; + try + { + bytes = Convert.FromBase64String(data); + } + catch (FormatException) + { + return null; + } + + // format: + // - 16 bytes of target file content hash (xxHash128) + // - int32 position (little endian) + // - utf-8 display filename + const int hashIndex = 0; + const int hashSize = 16; + const int positionIndex = hashIndex + hashSize; + const int positionSize = sizeof(int); + const int displayNameIndex = positionIndex + positionSize; + const int minLength = displayNameIndex; + + if (bytes.Length < minLength) + { + return null; + } + + var hash = bytes.AsMemory(start: hashIndex, length: hashSize); + var position = BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(start: positionIndex)); + + string displayFileName; + try + { + displayFileName = s_encoding.GetString(bytes, index: displayNameIndex, count: bytes.Length - displayNameIndex); + } + catch (ArgumentException) + { + return null; + } + + return (hash, position, displayFileName); + } + + // Note: the goal of implementing equality here is so that incremental state tables etc. can detect and use it. + // This encoding which uses the checksum of the referenced file may not be stable across incremental runs in practice, but it seems correct in principle to implement equality here anyway. + public override bool Equals(object? obj) + { + if ((object)this == obj) + return true; + + return obj is InterceptableLocation1 other + && _checksum.SequenceEqual(other._checksum) + && _path == other._path + && _position == other._position + && _lineNumberOneIndexed == other._lineNumberOneIndexed + && _characterNumberOneIndexed == other._characterNumberOneIndexed; + } + + public override int GetHashCode() + { + // Use only the _checksum and _position in the hash as these are the most distinctive fields of the location. + // i.e. if these are equal across instances, then other fields are likely to be equal as well. + return Hash.Combine( + BinaryPrimitives.ReadInt32LittleEndian(_checksum.AsSpan()), + _position); + } +} diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 8cfc786b84b88..6fec6e8ad114a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -337,6 +337,11 @@ Skupina &metody {0} se nedá převést na typ delegáta {1}. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Nedal se odvodit typ delegáta. @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + Argumenty typu pro metodu {0} nelze odvodit z použití, protože je použit argument s dynamickým typem a metoda má parametr kolekce parametrů mimo pole. Zkuste argumenty typu zadat explicitně. @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + Typ výrazu kolekce {0} musí mít instanci nebo metodu rozšíření Přidat, kterou lze volat jedním argumentem. Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + Typ výrazu kolekce musí mít použitelný konstruktor, který lze volat bez argumentů. @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + Cíl výrazu kolekce {0} nemá žádný typ elementu. @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + Nepoužívejte System.ParamArrayAttribute/System.Runtime.CompilerServices.ParamCollectionAttribute. Použijte místo něj klíčové slovo params. @@ -1107,6 +1112,31 @@ Experimentální funkce interceptors není v tomto oboru názvů povolená. Přidejte do projektu {0}. + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' Metoda UnmanagedCallersOnly {0} nemůže implementovat člena rozhraní {1} v typu {2}. @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + Modifikátory nejde umístit na použití deklarací. @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + Nejednoznačnost mezi rozbalenými a normálními tvary parametru kolekce parametrů mimo pole {0}, jediný odpovídající argument má dynamický typ. Zvažte přetypování dynamického argumentu. Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + Konstruktor {0} ponechá požadovaný člen {1} neinicializovaný. An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + Strom výrazů nesmí obsahovat rozšířenou formu parametru kolekce parametrů mimo pole. '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + {0} neobsahuje definici vhodné instance metody Přidat. Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + Vytvoření kolekce parametrů {0} vede k nekonečnému řetězu volání konstruktoru {1}. Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + Typ kolekce parametrů mimo pole musí mít použitelný konstruktor, který lze volat bez argumentů. Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + Metoda {0} nemůže být méně viditelná než člen s kolekcí parametrů {1}. The params parameter must have a valid collection type - The params parameter must have a valid collection type + Parametr params musí mít platný typ kolekce. @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + implicitní inicializační výraz indexeru @@ -2314,7 +2344,7 @@ Lock object - Lock object + Zamknout objekt @@ -2329,7 +2359,7 @@ params collections - params collections + kolekce parametrů @@ -2384,7 +2414,7 @@ string escape character - string escape character + řídicí znak řetězce @@ -2457,6 +2487,16 @@ ukazatel + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} není platná operace šíření v jazyce C# @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Hodnota typu System.Threading.Lock převedená na jiný typ použije pravděpodobně nezamýšlené zamykání na základě monitorování v příkazu lock. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Hodnota typu System.Threading.Lock převedená na jiný typ použije pravděpodobně nezamýšlené zamykání na základě monitorování v příkazu lock. @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Jedno nebo více přetížení konstruktoru s parametrem kolekce parametrů mimo pole může být použitelné pouze v rozbalené podobě, která není během dynamického odesílání podporována. One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Jedno nebo více přetížení konstruktoru s parametrem kolekce parametrů mimo pole může být použitelné pouze v rozbalené podobě, která není během dynamického odesílání podporována. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Jedno nebo více přetížení indexeru s parametrem kolekce parametrů mimo pole může být použitelné pouze v rozbalené podobě, která není během dynamického odesílání podporována. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Jedno nebo více přetížení indexeru s parametrem kolekce parametrů mimo pole může být použitelné pouze v rozbalené podobě, která není během dynamického odesílání podporována. One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Jedno nebo více přetížení metody{0} s parametrem kolekce parametrů mimo pole může být použitelné pouze v rozbalené podobě, která není během dynamického odesílání podporována. One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Jedno nebo více přetížení metody s parametrem kolekce parametrů mimo pole může být použitelné pouze v rozbalené podobě, která není během dynamického odesílání podporována. @@ -5109,13 +5149,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - Proměnná {0} {1}, která nemůže být null, musí při ukončování konstruktoru obsahovat hodnotu, která není null. Zvažte možnost deklarovat {0} jako proměnnou s možnou hodnotou null. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + Proměnná {0} {1}, která nemůže být null, musí při ukončování konstruktoru obsahovat hodnotu, která není null. Zvažte možnost deklarovat {0} jako proměnnou s možnou hodnotou null. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - Pole, které nemůže být null, musí při ukončování konstruktoru obsahovat hodnotu, která není null. Zvažte možnost deklarovat ho jako pole s možnou hodnotou null. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + Pole, které nemůže být null, musí při ukončování konstruktoru obsahovat hodnotu, která není null. Zvažte možnost deklarovat ho jako pole s možnou hodnotou null. @@ -10973,7 +11013,7 @@ Potlačení upozornění zvažte jenom v případě, když určitě nechcete če Cannot specify a default value for a parameter collection - Nejde zadat výchozí hodnotu pro pole parametrů. + Nejde zadat výchozí hodnotu pro kolekci parametrů. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 296db899ac590..821ae30bed560 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -337,6 +337,11 @@ Die &Methodengruppe "{0}" kann nicht in den Delegattyp "{1}" konvertiert werden. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Der Delegattyp konnte nicht abgeleitet werden. @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + Die Typargumente für die Methode "{0}" können nicht aus der Verwendung abgeleitet werden, da ein Argument mit dynamischem Typ verwendet wird und die Methode einen nicht arraybasierten Params-Auflistungsparameter aufweist. Geben Sie die Typargumente explizit an. @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + Der Auflistungsausdruckstyp "{0}" muss über eine Instanz oder die Erweiterungsmethode "Add" verfügen, die mit einem einzelnen Argument aufgerufen werden kann. Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + Der Auflistungsausdruckstyp muss über einen anwendbaren Konstruktor verfügen, der ohne Argumente aufgerufen werden kann. @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + Das Auflistungsausdrucksziel „{0}“ weist keinen Elementtyp auf. @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + Verwenden Sie nicht "System.ParamArrayAttribute"/"System.Runtime.CompilerServices.ParamCollectionAttribute". Verwenden Sie stattdessen das Schlüsselwort "Params". @@ -1107,6 +1112,31 @@ Das experimentelle Feature "Interceptors" ist nicht in diesem Namespace aktiviert. Fügen Sie Ihrem Projekt "{0}" hinzu. + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' Die Methode „UnmanagedCallersOnly“ „{0}“ kann das Schnittstellenelement „{1}“ im Typ „{2}“ nicht implementieren. @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + Modifizierer können nicht auf Using-Deklarationen platziert werden @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + Mehrdeutigkeit zwischen erweiterten und normalen Formen des nicht arraybasierten Params-Auflistungsparameter von "{0}". Das einzige entsprechende Argument hat den Typ "dynamic". Erwägen Sie die Umwandlung des dynamischen Arguments. Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + Der Konstruktor "{0}" lässt den erforderlichen Member "{1}" deinitialisiert. An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + Eine Ausdrucksstruktur darf keine erweiterte Form eines nicht arraybasierten Params-Auflistungsparameter enthalten. '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + "{0}" enthält keine Definition für eine "Add"-Methode für geeignete Instanzen Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + Die Erstellung der Params-Auflistung "{0}" führt zu einer unendlichen Aufrufkette des Konstruktors "{1}". Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + Nicht arraybasierten Params-Auflistungsparametertypen müssen über einen anwendbaren Konstruktor verfügen, der ohne Argumente aufgerufen werden kann. Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + Die Methode "{0}" darf nicht weniger sichtbar sein als der Member mit der Params-Auflistung "{1}". The params parameter must have a valid collection type - The params parameter must have a valid collection type + Der Params-Parameter muss einen gültigen Sammlungstyp aufweisen @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + impliziter Indexerinitialisierer @@ -2314,7 +2344,7 @@ Lock object - Lock object + Objekt sperren @@ -2329,7 +2359,7 @@ params collections - params collections + Params-Sammlungen @@ -2384,7 +2414,7 @@ string escape character - string escape character + Zeichenfolge – Escapezeichen @@ -2457,6 +2487,16 @@ Zeiger + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} ist kein gültiger C#-Überfüllungsvorgang. @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Ein Wert vom Typ „System.Threading.Lock“, der in einen anderen Typ konvertiert wurde, verwendet wahrscheinlich eine unbeabsichtigte monitorbasierte Sperrung in der Anweisung „lock“. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Ein Wert vom Typ „System.Threading.Lock“, der in einen anderen Typ konvertiert wurde, verwendet wahrscheinlich eine unbeabsichtigte monitorbasierte Sperrung in der Anweisung „lock“. @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Mindestens eine Konstruktorüberladung mit nicht arraybasierten Params-Auflistungsparametern kann nur in erweiterter Form anwendbar sein, was während der dynamischen Verteilung nicht unterstützt wird. One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Mindestens eine Konstruktorüberladung mit nicht arraybasierten Params-Auflistungsparametern kann nur in erweiterter Form anwendbar sein, was während der dynamischen Verteilung nicht unterstützt wird. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Eine oder mehrere Indexerüberladungen mit nicht arraybasierten Params-Auflistungsparametern sind möglicherweise nur in erweiterter Form anwendbar, was während der dynamischen Verteilung nicht unterstützt wird. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Eine oder mehrere Indexerüberladungen mit nicht arraybasierten Params-Auflistungsparametern sind möglicherweise nur in erweiterter Form anwendbar, was während der dynamischen Verteilung nicht unterstützt wird. One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Eine oder mehrere Überladungen der Methode "{0}" mit nicht arraybasierten Params-Auflistungsparametern sind möglicherweise nur in erweiterter Form anwendbar, was während der dynamischen Verteilung nicht unterstützt wird. One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Eine oder mehrere Überladungen der Methode mit nicht arraybasierten Params-Auflistungsparametern sind möglicherweise nur in erweiterter Form anwendbar, was während der dynamischen Verteilung nicht unterstützt wird. @@ -5109,13 +5149,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - Non-Nullable-{0} "{1}" muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie eine Deklaration von "{0}" als Nullable. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + Non-Nullable-{0} "{1}" muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie eine Deklaration von "{0}" als Nullable. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie die Deklaration als Nullable. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie die Deklaration als Nullable. @@ -10973,7 +11013,7 @@ Sie sollten das Unterdrücken der Warnung nur in Betracht ziehen, wenn Sie siche Cannot specify a default value for a parameter collection - Es kann kein Standardwert für ein Parameterarray angegeben werden. + Für eine Parameterauflistung kann kein Standardwert angegeben werden diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 97696daf3fc40..90ad6e3ef1e4d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -337,6 +337,11 @@ No se puede convertir el grupo de métodos '{0}' al tipo delegado '{1}'. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. El tipo de delegado no se puede deducir. @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + Los argumentos de tipo para el método "{0}" no se pueden inferir del uso porque se usa un argumento con tipo dinámico y el método tiene un parámetro de colección params no matriz. Intente especificar los argumentos de tipo explícitamente. @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + El tipo de expresión de colección "{0}" debe tener una instancia o un método de extensión "Add" al que se pueda llamar con un único argumento. Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + El tipo de expresión de colección debe tener un constructor aplicable al que se pueda llamar sin argumentos. @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + El destino de la expresión de colección '{0}' no tiene tipo de elemento. @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + No use "System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute". Use la palabra clave "params" en su lugar. @@ -1107,6 +1112,31 @@ La característica experimental "interceptores" no está habilitada en este espacio de nombres. Agregue '{0}' al proyecto. + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' El método "UnmanagedCallersOnly" "{0}" no puede implementar el miembro de interfaz "{1}" en el tipo "{2}" @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + Los modificadores no se pueden colocar en declaración "using" @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + Ambigüedad entre las formas expandidas y normales del parámetro de colección params no matriz de "{0}", el único argumento correspondiente tiene el tipo "dynamic". Considere la posibilidad de convertir el argumento dinámico. Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + El constructor "{0}" deja el miembro requerido "{1}" sin inicializar. An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + Un árbol de expresión no puede contener una forma expandida de parámetro de colección params no matriz. '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + "{0}" no contiene una definición para un método "Add" de instancia adecuado Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + La creación de la colección params "{0}" da como resultado una cadena infinita de invocación del constructor "{1}". Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + El tipo de colección params no matriz que debe tener un constructor aplicable al que se pueda llamar sin argumentos. Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + El método "{0}" no puede ser menos visible que el miembro con la colección params "{1}". The params parameter must have a valid collection type - The params parameter must have a valid collection type + El parámetro params debe tener un tipo de colección válido @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + inicializador de indizador implícito @@ -2314,7 +2344,7 @@ Lock object - Lock object + Bloquear objeto @@ -2329,7 +2359,7 @@ params collections - params collections + colecciones params @@ -2384,7 +2414,7 @@ string escape character - string escape character + carácter de escape de cadena @@ -2457,6 +2487,16 @@ puntero + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} no es una operación de propagación de C# válida @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Un valor de tipo 'System.Threading.Lock' convertido a otro tipo probablemente usará un bloqueo basado en monitor no deseado en la instrucción 'lock' . A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Un valor de tipo 'System.Threading.Lock' convertido a otro tipo probablemente usará un bloqueo basado en monitor no deseado en la instrucción 'lock' . @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Una o más sobrecargas del constructor que tienen un parámetro de colección params no matriz que podrían ser aplicables solo en forma expandida, lo que no se admite durante el envío dinámico. One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Una o más sobrecargas del constructor que tienen un parámetro de colección params no matriz que podrían ser aplicables solo en forma expandida, lo que no se admite durante el envío dinámico. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Una o más sobrecargas del indexador que tienen un parámetro de colección params no matriz que podrían ser aplicables solo en forma expandida, lo que no se admite durante el envío dinámico. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Una o más sobrecargas del indexador que tienen un parámetro de colección params no matriz que podrían ser aplicables solo en forma expandida, lo que no se admite durante el envío dinámico. One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Una o varias sobrecargas del método "{0}" que tienen un parámetro de colección params no matriz que podrían ser aplicables solo en forma expandida, lo que no se admite durante el envío dinámico. One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Una o varias sobrecargas del método que tienen un parámetro de colección params no matriz que podrían ser aplicables solo en forma expandida, lo que no se admite durante el envío dinámico. @@ -5109,13 +5149,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - El elemento {0} "{1}" que no acepta valores NULL debe contener un valor distinto de NULL al salir del constructor. Considere la posibilidad de declarar el elemento {0} como que admite un valor NULL. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + El elemento {0} "{1}" que no acepta valores NULL debe contener un valor distinto de NULL al salir del constructor. Considere la posibilidad de declarar el elemento {0} como que admite un valor NULL. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - Un campo que no acepta valores NULL debe contener un valor distinto de NULL al salir del constructor. Considere la posibilidad de declararlo como que admite un valor NULL. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + Un campo que no acepta valores NULL debe contener un valor distinto de NULL al salir del constructor. Considere la posibilidad de declararlo como que admite un valor NULL. @@ -10973,7 +11013,7 @@ Considere la posibilidad de suprimir la advertencia solo si tiene la seguridad d Cannot specify a default value for a parameter collection - No se puede especificar un valor predeterminado para una matriz de parámetros + No se puede especificar un valor predeterminado para una colección de parámetros diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 6bcd5e346729f..0a7db42d0fa35 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -337,6 +337,11 @@ Impossible de convertir le groupe de &méthodes '{0}' en type délégué '{1}'. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Impossible de déduire le type délégué. @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + Désolé... Nous ne pouvons pas déduire les arguments de type pour la méthode « {0} » à partir de l’utilisation, car un argument avec un type dynamique est utilisé et la méthode a un paramètre de collection de params non-tableau. Essayez de spécifier explicitement les arguments de type. @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + Le type d’expression de collection « {0} » doit avoir une instance ou une méthode d’extension « Ajouter » qui peut être appelée avec un seul argument. Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + Le type d’expression de collection doit avoir un constructeur applicable que vous pouvez appeler sans argument. @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + La cible « {0} » de l’expression de collection n’a aucun type d’élément. @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + N’utilisez pas « System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute ». Utilisez plutôt le mot clé « params ». @@ -1107,6 +1112,31 @@ La fonctionnalité expérimentale « intercepteurs » n'est pas activée dans cet espace de noms. Ajoutez « {0} » à votre projet. + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' La méthode UnmanagedCallersOnly '{0}' ne peut pas implémenter le membre d'interface '{1}' dans le type '{2}' @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + Les modificateurs ne peuvent pas être placés en utilisant des déclarations @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + Il existe une ambiguïté entre les formes développées et normales d’un paramètre de collection de params non-tableau de « {0} », le seul argument correspondant est de type « dynamique ». Envisagez d’effectuer un cast de l’argument dynamique. Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + Le constructeur « {0} » laisse le membre « {1} » requis non initialisé. An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + Une arborescence d’expression ne peut pas contenir une forme développée de paramètre de collection de params non-tableau. '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + « {0} » ne contient pas de définition pour une méthode « Add » d’instance appropriée Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + La création de la collection de params « {0} » entraîne une chaîne infinie d’invocation du constructeur « {1} ». Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + Le type de collection de params non-tableau doit avoir un constructeur applicable que vous pouvez appeler sans argument. Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + La méthode « {0} » peut pas être moins visible que le membre avec la collection de params « {1} ». The params parameter must have a valid collection type - The params parameter must have a valid collection type + Le paramètre params doit avoir un type de collection valide @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + initialiseur d’indexeur implicite @@ -2314,7 +2344,7 @@ Lock object - Lock object + Verrouiller des objets @@ -2329,7 +2359,7 @@ params collections - params collections + collections de params @@ -2384,7 +2414,7 @@ string escape character - string escape character + caractère d’échappement de chaîne @@ -2457,6 +2487,16 @@ aiguille + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} n’est pas une opération de propagation C# valide @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Une valeur de type « System.Threading.Lock » convertie en un autre type va probablement utiliser un verrouillage inattendu basé sur un moniteur dans l’instruction « lock ». A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Une valeur de type « System.Threading.Lock » convertie en un autre type va probablement utiliser un verrouillage inattendu basé sur un moniteur dans l’instruction « lock ». @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Il est possible qu’une ou plusieurs surcharges de constructeur ayant un paramètre de collection de params non-tableau soient applicables uniquement sous une forme développée, ce qui n’est pas pris en charge lors d’une répartition dynamique. One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Il est possible qu’une ou plusieurs surcharges de constructeur ayant un paramètre de collection de params non-tableau soient applicables uniquement sous une forme développée, ce qui n’est pas pris en charge lors d’une répartition dynamique. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Il est possible qu’une ou plusieurs surcharges d’indexeur ayant un paramètre de collection de params non-tableau soient applicables uniquement sous une forme développée, ce qui n’est pas pris en charge lors d’une répartition dynamique. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Il est possible qu’une ou plusieurs surcharges d’indexeur ayant un paramètre de collection de params non-tableau soient applicables uniquement sous une forme développée, ce qui n’est pas pris en charge lors d’une répartition dynamique. One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Il est possible qu’une ou plusieurs surcharges de méthode « {0} » ayant un paramètre de collection de params non-tableau soient applicables uniquement sous une forme développée, ce qui n’est pas pris en charge lors d’une répartition dynamique. One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Il est possible qu’une ou plusieurs surcharges de méthode ayant un paramètre de collection de params non-tableau soient applicables uniquement sous une forme développée, ce qui n’est pas pris en charge lors d’une répartition dynamique. @@ -5109,13 +5149,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - Le {0} '{1}' non-nullable doit contenir une valeur non-null lors de la fermeture du constructeur. Envisagez de déclarer le {0} comme nullable. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + Le {0} '{1}' non-nullable doit contenir une valeur non-null lors de la fermeture du constructeur. Envisagez de déclarer le {0} comme nullable. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - Un champ non-nullable doit contenir une valeur non-null lors de la fermeture du constructeur. Envisagez de déclarer le champ comme nullable. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + Un champ non-nullable doit contenir une valeur non-null lors de la fermeture du constructeur. Envisagez de déclarer le champ comme nullable. @@ -10973,7 +11013,7 @@ Supprimez l'avertissement seulement si vous êtes sûr de ne pas vouloir attendr Cannot specify a default value for a parameter collection - Impossible de spécifier une valeur par défaut pour un tableau de paramètres + Désolé... Nous ne pouvons pas spécifier une valeur par défaut pour une collection de paramètres diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 1fdd574f15cc6..f6f7038d7ac48 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -337,6 +337,11 @@ Non è possibile convertire il gruppo di &metodi '{0}' nel tipo delegato '{1}'. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Non è possibile dedurre il tipo di delegato. @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + Impossibile dedurre dall'utilizzo gli argomenti di tipo generico per il metodo '{0}' poiché è utilizzato un argomento con tipo dinamico e il metodo include un parametro di raccolta dei parametri non di matrice. Provare a specificare gli argomenti di tipo generico in modo esplicito. @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + Il tipo di espressione della raccolta "{0}" deve avere un'istanza o un metodo di estensione "Add" da poter chiamare con un argomento singolo. Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + Il tipo di espressione della raccolta deve avere un costruttore applicabile che può essere chiamato senza argomenti. @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + La destinazione dell'espressione di raccolta '{0}' non contiene alcun tipo di elemento. @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + Non usare 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Al suo posto, usare la parola chiave 'params'. @@ -1107,6 +1112,31 @@ La funzionalità sperimentale 'intercettori' non è abilitata in questo spazio dei nomi. Aggiungere '{0}' al progetto. + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' Il metodo '{0}' di 'UnmanagedCallersOnly' non può implementare il membro di interfaccia '{1}' nel tipo '{2}' @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + Non è possibile inserire modificatori nelle dichiarazioni using @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + Ambiguità tra forme espanse e normali del parametro di raccolta parametri non di matrice di '{0}'. L'unico argomento corrispondente ha il tipo 'dynamic'. Provare a eseguire il cast dell'argomento dinamico. Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + Il costruttore '{0}' lascia non inizializzato il membro richiesto '{1}'. An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + Un albero delle espressioni non può contenere un formato espanso di parametro di raccolta dei parametri non di matrice. '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + '{0}' non contiene una definizione per un metodo 'Add' di istanza appropriato Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + La creazione della raccolta di parametri '{0}' comporta una catena infinita di chiamate del costruttore '{1}'. Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + Il tipo di raccolta dei parametri non di matrice deve avere un costruttore applicabile che può essere chiamato senza argomenti. Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + Il metodo '{0}' non può essere meno visibile del membro con raccolta parametri '{1}'. The params parameter must have a valid collection type - The params parameter must have a valid collection type + Il parametro params deve avere un tipo di raccolta valido @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + inizializzatore indicizzatore implicito @@ -2314,7 +2344,7 @@ Lock object - Lock object + Blocca oggetto @@ -2329,7 +2359,7 @@ params collections - params collections + raccolte parametri @@ -2384,7 +2414,7 @@ string escape character - string escape character + carattere di escape stringa @@ -2457,6 +2487,16 @@ indicatore di misura + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} non è un'operazione estensione C# valida @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Un valore di tipo 'System.Threading.Lock' convertito in un tipo diverso userà probabilmente un blocco basato su monitoraggio non intenzionale nell'istruzione 'lock'. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Un valore di tipo 'System.Threading.Lock' convertito in un tipo diverso userà probabilmente un blocco basato su monitoraggio non intenzionale nell'istruzione 'lock'. @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uno o più overload del costruttore con parametro di raccolta dei parametri non di matrice possono essere applicati solo in formato espanso che non è supportato durante l'invio dinamico. One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uno o più overload del costruttore con parametro di raccolta dei parametri non di matrice possono essere applicati solo in formato espanso che non è supportato durante l'invio dinamico. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uno o più overload dell'indicizzatore con parametro di raccolta dei parametri non di matrice possono essere applicati solo in formato espanso che non è supportato durante l'invio dinamico. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uno o più overload dell'indicizzatore con parametro di raccolta dei parametri non di matrice possono essere applicati solo in formato espanso che non è supportato durante l'invio dinamico. One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uno o più overload del metodo '{0}' con parametro di raccolta dei parametri non di matrice possono essere applicati solo in formato espanso che non è supportato durante l'invio dinamico. One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uno o più overload del metodo con parametro di raccolta dei parametri non di matrice possono essere applicati solo in formato espanso che non è supportato durante l'invio dinamico. @@ -5109,13 +5149,13 @@ target:module Compila un modulo che può essere aggiunto ad altro - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - L'elemento {0} '{1}' non nullable deve contenere un valore non Null all'uscita dal costruttore. Provare a dichiarare {0} come nullable. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + L'elemento {0} '{1}' non nullable deve contenere un valore non Null all'uscita dal costruttore. Provare a dichiarare {0} come nullable. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - Il campo non nullable deve contenere un valore non Null all'uscita dal costruttore. Provare a dichiararlo come nullable. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + Il campo non nullable deve contenere un valore non Null all'uscita dal costruttore. Provare a dichiararlo come nullable. @@ -10973,7 +11013,7 @@ Come procedura consigliata, è consigliabile attendere sempre la chiamata. Cannot specify a default value for a parameter collection - Impossibile specificare un valore predefinito per una matrice di parametri + Impossibile specificare un valore predefinito per una raccolta di parametri diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 3ab67482f019f..9b9597c936270 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -337,6 +337,11 @@ メソッド グループ '{0}' をデリゲート型 '{1}' に変換することはできません。(&M) + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. デリゲート型を推論できませんでした。 @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + メソッド '{0}' の型引数を使用法から推論できません。これは動的型のある引数が使用され、メソッドに配列以外の params コレクション パラメーターがあるためです。型引数を明示的に指定してみてください。 @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + コレクション式の型 '{0}' には、1 つの引数で呼び出すことができるインスタンスメソッドまたは拡張メソッド 'Add' が必要です。 Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + コレクション式の型には、引数なしで呼び出すことができる適用可能なコンストラクターが必要です。 @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + コレクション式のターゲット '{0}' に要素型がありません。 @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute' は使用しないでください。代わりに 'params' キーワードを使用してください。 @@ -994,7 +999,7 @@ Method '{0}' must be non-generic to match '{1}'. - '{1}' に一致するには、メソッド '{0}' は非ジェネリックである必要があります。 + '{0}' に一致するには、メソッド '{1}' は非ジェネリックである必要があります。 @@ -1107,6 +1112,31 @@ 'インターセプター' の実験的な機能は、この名前空間では有効になっていません。プロジェクトに '{0}' を追加します。 + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' 'UnmanagedCallersOnly' メソッド '{0}' は、インターフェイス メンバー '{1}' を型 '{2}' で実装できません @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + 宣言を使用して修飾子を配置することはできません @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + '{0}' の非配列 params コレクション パラメーターの展開形式と通常の形式の間のあいまいさです。対応する引数の型は 'dynamic' のみです。動的引数のキャストを検討してください。 Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + コンストラクター '{0}' は、必要なメンバー '{1}' を初期化しないままにします。 An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + 式ツリーに、配列以外の params コレクション パラメーターの展開形式を含めないようにしてください。 '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + '{0}' には、適切なインスタンス 'Add' メソッドの定義が含まれていません Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + パラメーター コレクション '{0}' を作成すると、コンストラクター '{1}' の呼び出しのチェーンが無限になります。 Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + 配列 params コレクション以外の種類には、引数なしで呼び出すことができる適用可能なコンストラクターが必要です。 Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + メソッド '{0}' は params コレクション '{1}' を持つメンバーより低く表示できません。 The params parameter must have a valid collection type - The params parameter must have a valid collection type + params パラメーターには有効なコレクションの種類が必要です @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + 暗黙的なインデクサー初期化子 @@ -2314,7 +2344,7 @@ Lock object - Lock object + オブジェクトをロックする @@ -2329,7 +2359,7 @@ params collections - params collections + params コレクション @@ -2384,7 +2414,7 @@ string escape character - string escape character + 文字列エスケープ文字 @@ -2457,6 +2487,16 @@ ポインター + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} は有効な C# 分散演算ではありません @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + 型 'System.Threading.Lock' の値が別の型に変換されると、'lock' ステートメントで意図しない可能性の高いモニターベースのロックが使用されます。 A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + 型 'System.Threading.Lock' の値が別の型に変換されると、'lock' ステートメントで意図しない可能性の高いモニターベースのロックが使用されます。 @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 配列 params 以外のコレクション パラメーターを持つ 1 つ以上のコンストラクター オーバーロードは、動的ディスパッチ中にサポートされない拡張形式でのみ適用できます。 One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 配列 params 以外のコレクション パラメーターを持つ 1 つ以上のコンストラクター オーバーロードは、動的ディスパッチ中にサポートされない拡張形式でのみ適用できます。 One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 配列パラメーター以外のコレクション パラメーターを持つ 1 つ以上のインデクサー オーバーロードは、動的ディスパッチ中にサポートされない拡張形式でのみ適用できます。 One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 配列パラメーター以外のコレクション パラメーターを持つ 1 つ以上のインデクサー オーバーロードは、動的ディスパッチ中にサポートされない拡張形式でのみ適用できます。 One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 配列 params 以外のコレクション パラメーターを持つメソッド '{0}' の 1 つ以上のオーバーロードは、動的ディスパッチ中にサポートされない拡張形式でのみ適用できます。 One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 配列パラメーター以外のコレクション パラメーターを持つメソッドの 1 つ以上のオーバーロードは、動的ディスパッチ中にサポートされない拡張形式でのみ適用できます。 @@ -5109,13 +5149,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - null 非許容の {0} '{1}' には、コンストラクターの終了時に null 以外の値が入っていなければなりません。{0} を Null 許容として宣言することをご検討ください。 + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + null 非許容の {0} '{1}' には、コンストラクターの終了時に null 以外の値が入っていなければなりません。{0} を Null 許容として宣言することをご検討ください。 - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - null 非許容のフィールドには、コンストラクターの終了時に null 以外の値が入っていなければなりません。Null 許容として宣言することをご検討ください。 + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + null 非許容のフィールドには、コンストラクターの終了時に null 以外の値が入っていなければなりません。Null 許容として宣言することをご検討ください。 @@ -10973,7 +11013,7 @@ You should consider suppressing the warning only if you're sure that you don't w Cannot specify a default value for a parameter collection - パラメーター配列には既定値を指定できません + パラメーター コレクションには、既定値を指定できません diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index af60b68bc3d07..8b97635e28dc4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -337,6 +337,11 @@ 메서드 그룹 '{0}'을(를) 대리자 형식 '{1}'(으)로 변환할 수 없습니다(&M). + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. 대리자 형식을 유추할 수 없습니다. @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + 동적 형식을 가진 인수가 사용되고 메서드에 배열 매개 변수가 아닌 params 컬렉션 매개 변수가 있으므로 메서드 '{0}' 형식 인수를 사용법에서 유추할 수 없습니다. 형식 인수를 명시적으로 지정해 보세요. @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + 컬렉션 식 형식 '{0}'에는 단일 인수로 호출할 수 있는 인스턴스 또는 확장 메서드 'Add'가 있어야 합니다. Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + 컬렉션 식 형식에는 인수 없이 호출할 수 있는 적용 가능한 생성자가 있어야 합니다. @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + 컬렉션 식 대상 '{0}'에 요소 형식이 없습니다. @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'를 사용하지 마세요. 대신 'params' 키워드를 사용합니다. @@ -994,7 +999,7 @@ Method '{0}' must be non-generic to match '{1}'. - '{1}' 일치하려면 메서드 '{0}' 제네릭이 아니어야 합니다. + '{0}' 메서드는 '{1}'에 일치하려면 제네릭이 아니어야 합니다. @@ -1107,6 +1112,31 @@ 이 네임스페이스에서는 '인터셉터' 실험적 기능을 사용할 수 없습니다. 프로젝트에 '{0}'을(를) 추가하세요. + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' 'UnmanagedCallersOnly' 메서드 '{0}'은(는) '{2}' 유형의 인터페이스 멤버 '{1}'을(를) 구현할 수 없습니다. @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + 선언을 사용하여 한정자를 배치할 수 없습니다. @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + '{0}'의 배열 매개 변수가 아닌 params 컬렉션 매개 변수의 확장된 형식과 일반 형식 간의 모호성입니다. 해당 인수에는 'dynamic' 형식만 있습니다. 동적 인수를 캐스팅하는 것이 좋습니다. Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + 생성자 '{0}'은(는) 필수 멤버 '{1}'을(를) 초기화되지 않은 상태로 둡니다. An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + 식 트리에는 배열 매개 변수가 아닌 params 컬렉션 매개 변수의 확장된 형식을 포함할 수 없습니다. '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + '{0}'에 적합한 인스턴스 'Add' 메서드에 대한 정의가 포함되어 있지 않습니다. Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + params 컬렉션 '{0}'을(를) 만들면 생성자 '{1}’이(가) 무한 체인으로 호출됩니다. Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + 배열 매개 변수가 아닌 params 컬렉션 형식에는 인수 없이 호출할 수 있는 적용 가능한 생성자가 있어야 합니다. Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + '{0}' 메서드는 params 컬렉션이 '{1}'인 멤버보다 작게 표시할 수 없습니다. The params parameter must have a valid collection type - The params parameter must have a valid collection type + params 매개 변수는 유효한 컬렉션 형식이어야 합니다. @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + 암시적 인덱서 이니셜라이저 @@ -2314,7 +2344,7 @@ Lock object - Lock object + 개체 잠금 @@ -2329,7 +2359,7 @@ params collections - params collections + params 컬렉션 @@ -2384,7 +2414,7 @@ string escape character - string escape character + 문자열 이스케이프 문자 @@ -2457,6 +2487,16 @@ 포인터 + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0}은(는) 유효한 C# 확산 작업이 아닙니다. @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + 다른 형식으로 변환된 'System.Threading.Lock' 형식의 값은 ‘lock’ 문에서 의도하지 않은 모니터 기반 잠금을 사용합니다. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + 다른 형식으로 변환된 'System.Threading.Lock' 형식의 값은 ‘lock’ 문에서 의도하지 않은 모니터 기반 잠금을 사용합니다. @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 배열 매개 변수가 아닌 params 컬렉션 매개 변수가 있는 하나 이상의 생성자 오버로드는 동적 디스패치 중에 지원되지 않는 확장된 형식에서만 적용할 수 있습니다. One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 배열 매개 변수가 아닌 params 컬렉션 매개 변수가 있는 하나 이상의 생성자 오버로드는 동적 디스패치 중에 지원되지 않는 확장된 형식에서만 적용할 수 있습니다. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 배열 매개 변수가 아닌 params 컬렉션 매개 변수가 있는 하나 이상의 인덱서 오버로드는 동적 디스패치 중에 지원되지 않는 확장된 형식에서만 적용할 수 있습니다. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 배열 매개 변수가 아닌 params 컬렉션 매개 변수가 있는 하나 이상의 인덱서 오버로드는 동적 디스패치 중에 지원되지 않는 확장된 형식에서만 적용할 수 있습니다. One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 배열 매개 변수가 아닌 params 컬렉션 매개 변수가 있는 '{0}' 메서드의 하나 이상의 오버로드는 동적 디스패치 중에 지원되지 않는 확장된 형식에서만 적용할 수 있습니다. One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 배열 매개 변수가 아닌 params 컬렉션 매개 변수가 있는 메서드의 오버로드 하나 이상은 동적 디스패치 중에 지원되지 않는 확장된 형식에서만 적용할 수 있습니다. @@ -5109,13 +5149,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - 생성자를 종료할 때 null을 허용하지 않는 {0} '{1}'에 null이 아닌 값을 포함해야 합니다. {0}을(를) null 허용으로 선언해 보세요. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + 생성자를 종료할 때 null을 허용하지 않는 {0} '{1}'에 null이 아닌 값을 포함해야 합니다. {0}을(를) null 허용으로 선언해 보세요. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - 생성자를 종료할 때 null을 허용하지 않는 필드에 null이 아닌 값을 포함해야 합니다. null 허용으로 선언해 보세요. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + 생성자를 종료할 때 null을 허용하지 않는 필드에 null이 아닌 값을 포함해야 합니다. null 허용으로 선언해 보세요. @@ -10973,7 +11013,7 @@ You should consider suppressing the warning only if you're sure that you don't w Cannot specify a default value for a parameter collection - 매개 변수 배열의 기본값을 지정할 수 없습니다. + 매개 변수 컬렉션의 기본값을 지정할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index c9b6f65041b02..64914854cbbf0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -337,6 +337,11 @@ Nie można przekonwertować &grupy metod „{0}” na typ delegata „{1}”. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Nie można wywnioskować typu delegowania. @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + Argumenty typu dla metody „{0}” nie mogą być wywnioskowane z użycia, ponieważ używany jest argument typu dynamicznego, a metoda ma parametr kolekcji params inny niż tablica. Spróbuj jawnie określić argumenty typu. @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + Typ wyrażenia kolekcji „{0}” musi mieć wystąpienie lub metodę rozszerzenia „Dodaj”, którą można wywołać z pojedynczym argumentem. Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + Typ wyrażenia kolekcji musi mieć odpowiedni konstruktor, który można wywołać bez argumentów. @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + Wyrażenie kolekcji docelowej „{0}” nie ma typu elementu. @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + Nie używaj atrybutu „System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute”. Zamiast niego użyj słowa kluczowego „params”. @@ -1107,6 +1112,31 @@ Funkcja eksperymentalna „interceptorów” nie jest włączona w tej przestrzeni nazw. Dodaj „{0}” do swojego projektu. + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' Metoda "UnmanagedCallersOnly" "{0}" nie może implementować składowej interfejsu "{1}" w typie "{2}" @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + Modyfikatorów nie można umieszczać przy użyciu deklaracji usług @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + Niejednoznaczność między rozszerzoną i normalną formą parametru kolekcji params niebędącego tablicą „{0}”, jedyny odpowiadający argument ma typ dynamiczny. Rozważ rzutowanie argumentu dynamicznego. Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + Konstruktor „{0}” pozostawia wymaganą składową "{1}". An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + Drzewo wyrażeń nie może zawierać rozszerzonej formy parametru kolekcji params innego niż tablica. '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + Element „{0}” nie zawiera definicji odpowiedniej metody „Dodaj” wystąpienia Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + Utworzenie kolekcji parametrów „{0}” powoduje nieskończony łańcuch wywołania konstruktora „{1}”. Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + Typ kolekcji niebędący tablicą parametrów musi mieć odpowiedni konstruktor, który można wywołać bez argumentów. Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + Metoda „{0}” nie może być mniej widoczna niż składowa z kolekcją parametrów „{1}”. The params parameter must have a valid collection type - The params parameter must have a valid collection type + Parametr params musi mieć prawidłowy typ kolekcji @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + niejawny inicjator indeksatora @@ -2314,7 +2344,7 @@ Lock object - Lock object + Zablokuj obiekt @@ -2329,7 +2359,7 @@ params collections - params collections + kolekcje params @@ -2384,7 +2414,7 @@ string escape character - string escape character + ciąg — znak ucieczki @@ -2457,6 +2487,16 @@ wskaźnik + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} nie jest prawidłową operacją rozłożenia w języku C# @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Wartość typu „System.Threading.Lock” przekonwertowana na inny typ użyje prawdopodobnie niezamierzonego blokowania opartego na monitorze w instrukcji „zablokuj”. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Wartość typu „System.Threading.Lock” przekonwertowana na inny typ użyje prawdopodobnie niezamierzonego blokowania opartego na monitorze w instrukcji „zablokuj”. @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Jedno lub więcej przeciążeń konstruktora z parametrem kolekcji parametrów niebędącym tablicą może mieć zastosowanie tylko w rozszerzonej formie, która nie jest obsługiwana podczas dynamicznego wysyłania. One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Jedno lub więcej przeciążeń konstruktora z parametrem kolekcji parametrów niebędącym tablicą może mieć zastosowanie tylko w rozszerzonej formie, która nie jest obsługiwana podczas dynamicznego wysyłania. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Jedno lub więcej przeciążeń indeksatora z parametrem kolekcji niebędącym tablicą parametrów może mieć zastosowanie tylko w rozszerzonej formie, która nie jest obsługiwana podczas dynamicznego wysyłania. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Jedno lub więcej przeciążeń indeksatora z parametrem kolekcji niebędącym tablicą parametrów może mieć zastosowanie tylko w rozszerzonej formie, która nie jest obsługiwana podczas dynamicznego wysyłania. One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Co najmniej jedno przeciążenie metody „{0}” z parametrem kolekcji params innych niż tablicowe może mieć zastosowanie tylko w rozszerzonej formie, która nie jest obsługiwana podczas dynamicznego wysyłania. One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Co najmniej jedno przeciążenie metody posiadającej parametr kolekcji params inny niż tablica może mieć zastosowanie tylko w rozszerzonej formie, która nie jest obsługiwana podczas dynamicznego wysyłania. @@ -5109,13 +5149,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - Niedopuszczający wartości null element {0} „{1}” musi zawierać wartość inną niż null podczas kończenia działania konstruktora. Rozważ zadeklarowanie elementu {0} jako dopuszczającego wartość null. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + Niedopuszczający wartości null element {0} „{1}” musi zawierać wartość inną niż null podczas kończenia działania konstruktora. Rozważ zadeklarowanie elementu {0} jako dopuszczającego wartość null. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - Pole niedopuszczające wartości null musi zawierać wartość inną niż null podczas kończenia działania konstruktora. Rozważ zadeklarowanie pola jako dopuszczającego wartość null. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + Pole niedopuszczające wartości null musi zawierać wartość inną niż null podczas kończenia działania konstruktora. Rozważ zadeklarowanie pola jako dopuszczającego wartość null. @@ -10973,7 +11013,7 @@ Pominięcie ostrzeżenia należy wziąć pod uwagę tylko w sytuacji, gdy na pew Cannot specify a default value for a parameter collection - Nie można określić wartości domyślnej dla tablicy parametrów + Nie można określić wartości domyślnej dla tablicy parametrów diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 2dba1c7baee45..b900c37d4c5ac 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -337,6 +337,11 @@ Não é possível converter o grupo &método '{0}' para o tipo delegado '{1}'. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. O tipo de representante não pôde ser inferido. @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + Os argumentos de tipo para o método '{0}' não podem ser inferidos a partir do uso porque um argumento com tipo dinâmico é usado e o método tem um parâmetro de coleção de params que não é uma matriz. Tente especificar explicitamente os argumentos de tipo. @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + O tipo de expressão de coleção '{0}' deve ter uma instância ou método de extensão 'Add' que pode ser chamado com apenas um argumento. Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + O tipo de expressão de coleção precisa ter um construtor aplicável que possa ser chamado sem argumentos. @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + O destino da expressão de coleção "{0}" não tem nenhum tipo de elemento. @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + Não use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Em vez disso, use a palavra-chave 'params'. @@ -1107,6 +1112,31 @@ O recurso experimental “interceptadores” não está habilitado neste namespace. Adicione “{0}” ao seu projeto. + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' O método 'UnmanagedCallersOnly' '{0}' não pode implementar o membro de interface '{1}' no tipo '{2}' @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + Os modificadores não podem ser colocados em declarações de uso @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + Ambiguidade entre as formas expandida e normal do parâmetro de coleção de params que não é uma matriz de '{0}', o único argumento correspondente tem o tipo 'dynamic'. Considere a conversão do argumento dinâmico. Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + O construtor '{0}' deixa o membro obrigatório '{1}' não inicializado. An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + Uma árvore de expressão não pode conter uma forma expandida de parâmetro de coleção de params que não seja uma matriz. '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + '{0}' não contém uma definição para um método 'Add' de instância adequado Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + A criação da coleção de params '{0}' resulta em uma cadeia infinita de invocação do construtor '{1}'. Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + Tipos de coleção de params que não sejam matrizes devem ter um construtor aplicável que possa ser chamado sem argumentos. Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + O método '{0}' não pode ter uma visibilidade menor que o membro com a coleção de params '{1}'. The params parameter must have a valid collection type - The params parameter must have a valid collection type + O parâmetro params deve ter um tipo de coleção válido @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + inicializador de indexador implícito @@ -2314,7 +2344,7 @@ Lock object - Lock object + Bloquear objeto @@ -2329,7 +2359,7 @@ params collections - params collections + coleções de params @@ -2384,7 +2414,7 @@ string escape character - string escape character + cadeia de caracteres de escape @@ -2457,6 +2487,16 @@ ponteiro + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} não é uma operação de espalhamento C# válida @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Um valor do tipo "System.Threading.Lock" convertido em um tipo diferente usará o bloqueio baseado em monitor provavelmente não intencional na instrução "lock". A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Um valor do tipo "System.Threading.Lock" convertido em um tipo diferente usará o bloqueio baseado em monitor provavelmente não intencional na instrução "lock". @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uma ou mais sobrecargas de construtor com parâmetro de coleção de params que não é uma matriz podem ser aplicáveis somente na forma expandida, o que não tem suporte durante o envio dinâmico. One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uma ou mais sobrecargas de construtor com parâmetro de coleção de params que não é uma matriz podem ser aplicáveis somente na forma expandida, o que não tem suporte durante o envio dinâmico. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uma ou mais sobrecargas de indexador com parâmetro de coleção de params que não é uma matriz podem ser aplicáveis somente na forma expandida, o que não tem suporte durante o envio dinâmico. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uma ou mais sobrecargas de indexador com parâmetro de coleção de params que não é uma matriz podem ser aplicáveis somente na forma expandida, o que não tem suporte durante o envio dinâmico. One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Um ou mais métodos sobrecarregados '{0}' com parâmetro de coleção de params que não é uma matriz podem ser aplicáveis apenas em forma expandida, o que não é suportado durante o despacho dinâmico. One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Uma ou mais sobrecargas de método com parâmetro de coleção de params que não é uma matriz podem ser aplicáveis apenas na forma expandida, o que não tem suporte durante a expedição dinâmica. @@ -5109,13 +5149,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - O {0} não anulável '{1}' precisa conter um valor não nulo ao sair do construtor. Considere declarar o {0} como anulável. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + O {0} não anulável '{1}' precisa conter um valor não nulo ao sair do construtor. Considere declarar o {0} como anulável. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - O campo não anulável precisa conter um valor não nulo ao sair do construtor. Considere declará-lo como anulável. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + O campo não anulável precisa conter um valor não nulo ao sair do construtor. Considere declará-lo como anulável. @@ -10973,7 +11013,7 @@ Você pode suprimir o aviso se tiver certeza de que não vai querer aguardar a c Cannot specify a default value for a parameter collection - Não é possível especificar um valor padrão para uma matriz de parâmetros + Não é possível especificar um valor padrão para uma coleção de parâmetros diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 12a8569d53702..026c185df3a32 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -337,6 +337,11 @@ Невозможно преобразовать &группу методов "{0}" в тип делегата "{1}". + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Не удалось вывести тип делегата. @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + Аргументы типа для метода "{0}" не могут быть выведены из использования, поскольку используется аргумент с динамическим типом и метод имеет параметр коллекции params, не являющийся массивом. Попробуйте явно указать аргументы типа. @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + Тип выражения коллекции "{0}" должен иметь экземпляр или метод расширения "Add", который можно вызвать с помощью одного аргумента. Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + Тип выражения коллекции должен иметь применимый конструктор, который может быть вызван без аргументов. @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + У целевого объекта выражения "{0}" нет типа элемента. @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + Не используйте System.ParamArrayAttribute/System.Runtime.CompilerServices.ParamCollectionAttribute. Используйте ключевое слово "params". @@ -1107,6 +1112,31 @@ Экспериментальная функция "перехватчики" не включена в этом пространстве имен. Добавьте "{0}" в свой проект. + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' Метод UnmanagedCallersOnly "{0}" не может реализовать элемент интерфейса "{1}" в типе "{2}" @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + Модификаторы нельзя разместить при использовании объявлений. @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + Неоднозначность между расширенной и нормальной формами параметра коллекции params, отличных от массива, "{0}", единственный соответствующий аргумент имеет тип "dynamic". Рассмотрите возможность использования динамического аргумента. Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + Конструктор "{0}" оставляет необходимый элемент "{1}" не инициализированным. An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + Дерево выражений не может содержать расширенную форму параметра коллекции params, не являющегося массивом. '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + "{0}" не содержит определения для подходящего экземпляра метода "Add" Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + Создание коллекции params "{0}" приводит к бесконечной цепочке вызовов конструктора "{1}". Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + Тип коллекции params, не являющийся массивом, должен иметь применимый конструктор, который можно вызывать без аргументов. Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + Метод "{0}" не может быть менее видимым, чем элемент с коллекцией параметров "{1}". The params parameter must have a valid collection type - The params parameter must have a valid collection type + Параметр params должен иметь допустимый тип коллекции. @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + неявный инициализатор индексатора @@ -2314,7 +2344,7 @@ Lock object - Lock object + Блокировать объект @@ -2329,7 +2359,7 @@ params collections - params collections + коллекции params @@ -2384,7 +2414,7 @@ string escape character - string escape character + escape-символ строки @@ -2457,6 +2487,16 @@ указатель + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} не является допустимой операцией расширения C# @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Значение типа "System.Threading.Lock", преобразованное в другой тип, будет использовать (скорее всего, непреднамеренную) блокировку на основе монитора в инструкции "lock". A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Значение типа "System.Threading.Lock", преобразованное в другой тип, будет использовать (скорее всего, непреднамеренную) блокировку на основе монитора в инструкции "lock". @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Одна или несколько перегрузок конструктора, содержащих параметр коллекции params, отличный от массива, могут быть применимы только в расширенной форме, не поддерживаемой во время динамической отправки. One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Одна или несколько перегрузок конструктора, содержащих параметр коллекции params, отличный от массива, могут быть применимы только в расширенной форме, не поддерживаемой во время динамической отправки. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Одна или несколько перегрузок индексатора, содержащих параметр коллекции params, отличный от массива, могут быть применимы только в расширенной форме, не поддерживаемой во время динамической отправки. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Одна или несколько перегрузок индексатора, содержащих параметр коллекции params, отличный от массива, могут быть применимы только в расширенной форме, не поддерживаемой во время динамической отправки. One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Одна или несколько перегрузок метода "{0}", имеющих параметр коллекции params, отличный от массива, могут быть применимы только в расширенной форме, которая не поддерживается во время динамической отправки. One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Одна или несколько перегрузок метода, имеющих параметр коллекции params, отличный от массива, могут быть применимы только в расширенной форме, которая не поддерживается во время динамической отправки. @@ -5110,13 +5150,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - {0} "{1}", не допускающий значения NULL, должен содержать значение, отличное от NULL, при выходе из конструктора. Возможно, стоит объявить {0} как допускающий значения NULL. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + {0} "{1}", не допускающий значения NULL, должен содержать значение, отличное от NULL, при выходе из конструктора. Возможно, стоит объявить {0} как допускающий значения NULL. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - Поле, не допускающее значения NULL, должно содержать значение, отличное от NULL, при выходе из конструктора. Возможно, стоит объявить поле как допускающее значения NULL. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + Поле, не допускающее значения NULL, должно содержать значение, отличное от NULL, при выходе из конструктора. Возможно, стоит объявить поле как допускающее значения NULL. @@ -10974,7 +11014,7 @@ You should consider suppressing the warning only if you're sure that you don't w Cannot specify a default value for a parameter collection - Не удалось указать значение по умолчанию для массива параметров. + Не удается указать значение по умолчанию для коллекции параметров. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index c2f2c01fd7e71..981f0654d9592 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -337,6 +337,11 @@ '{0}' &metot grubu, '{1}' temsilci türüne dönüştürülemiyor. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Temsilci türü çıkarsanamadı. @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + Dinamik türe sahip bir bağımsız değişken kullanıldığından ve metot dizi olmayan params koleksiyon parametresine sahip olduğundan, '{0}' metodu için tür bağımsız değişkeni kullanımdan çıkarsanamıyor. Tür bağımsız değişkenlerini açıkça belirtmeyi deneyin. @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + '{0}' koleksiyon ifade türünün tek bir bağımsız değişken ile çağrılabilecek bir örnek veya 'Add' genişletme yöntemi içermesi gerekir. Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + Koleksiyon ifade türünün, bağımsız değişken olmadan çağrılabilecek geçerli bir oluşturucusu olmalıdır. @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + '{0}' koleksiyon ifadesi hedefinin öğe türü yok. @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute' kullanmayın. Bunun yerine 'params' anahtar sözcüğünü kullanın. @@ -1107,6 +1112,31 @@ 'Engelleyiciler' deneysel özelliği bu ad alanında etkin değil. Projenize '{0}' ekleyin. + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' '{0}' 'UnmanagedCallersOnly' yöntemi, '{1}' arabirim üyesini '{2}' türünde uygulayamaz @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + Değiştiriciler using bildirimlerine koyulamaz @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + '{0}' parametresinin dizi olmayan params koleksiyonu parametresinin genişletilmiş ve normal biçimleri arasında belirsizlik var, karşılık gelen tek bağımsız değişken 'dynamic' türüne sahip. Dinamik bağımsız değişkenin türünü dönüştürmeyi düşünün. Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + Oluşturucu '{0}', gerekli '{1}' üyesinin başlatmasını geri alıyor. An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + İfade ağacı, dizi olmayan params koleksiyonu parametresinin genişletilmiş bir formunu içeremez. '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + '{0}', uygun bir örnek 'Add' metodu için bir tanım içermiyor Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + Params koleksiyonu '{0}' oluşturma işlemi sonsuz bir oluşturucu '{1}' çağrısı zinciriyle sonuçlanıyor. Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + Dizi olmayan params koleksiyon türünün, bağımsız değişken olmadan çağrılabilecek geçerli bir oluşturucusu olmalıdır. Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + Metot '{0}', '{1}' params koleksiyonuna sahip üyeden daha az görünür olamaz. The params parameter must have a valid collection type - The params parameter must have a valid collection type + Params parametresi geçerli bir koleksiyon türüne sahip olmalıdır @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + örtük dizin oluşturucu başlatıcısı @@ -2314,7 +2344,7 @@ Lock object - Lock object + Nesne kilitleme @@ -2329,7 +2359,7 @@ params collections - params collections + params koleksiyonları @@ -2384,7 +2414,7 @@ string escape character - string escape character + dize kaçış karakteri @@ -2457,6 +2487,16 @@ işaretçi + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0}, geçerli bir C# yayma işlemi değil @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Farklı bir türe dönüştürülen 'System.Threading.Lock' türündeki bir değer, 'lock' deyiminde muhtemelen istenmeyen izleyici tabanlı kilitlemeyi kullanır. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + Farklı bir türe dönüştürülen 'System.Threading.Lock' türündeki bir değer, 'lock' deyiminde muhtemelen istenmeyen izleyici tabanlı kilitlemeyi kullanır. @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Dizi olmayan parametreler koleksiyonu parametresine sahip bir veya daha fazla oluşturucu aşırı yükleme yalnızca genişletilmiş formda uygulanıyor olabilir. Bu, dinamik dağıtım sırasında desteklenmez. One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Dizi olmayan parametreler koleksiyonu parametresine sahip bir veya daha fazla oluşturucu aşırı yükleme yalnızca genişletilmiş formda uygulanıyor olabilir. Bu, dinamik dağıtım sırasında desteklenmez. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Dizi olmayan parametreler koleksiyonu parametresine sahip bir veya daha fazla dizin oluşturucu aşırı yükleme yalnızca genişletilmiş formda uygulanıyor olabilir. Bu, dinamik dağıtım sırasında desteklenmez. One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Dizi olmayan parametreler koleksiyonu parametresine sahip bir veya daha fazla dizin oluşturucu aşırı yükleme yalnızca genişletilmiş formda uygulanıyor olabilir. Bu, dinamik dağıtım sırasında desteklenmez. One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Dizi olmayan parametreler koleksiyonu parametresine sahip '{0}' metodunun bir veya daha fazla aşırı yüklemesi yalnızca genişletilmiş formda uygulanıyor olabilir. Bu, dinamik dağıtım sırasında desteklenmez. One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Dizi olmayan parametreler koleksiyonu parametresine sahip metodun bir veya daha fazla aşırı yüklemesi yalnızca genişletilmiş formda uygulanıyor olabilir. Bu, dinamik dağıtım sırasında desteklenmez. @@ -5109,13 +5149,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - Null atanamaz {0} '{1}', oluşturucudan çıkış yaparken null olmayan bir değer içermelidir. {0} alanını null atanabilir olarak bildirmeyi düşünün. + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + Null atanamaz {0} '{1}', oluşturucudan çıkış yaparken null olmayan bir değer içermelidir. {0} alanını null atanabilir olarak bildirmeyi düşünün. - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - Null atanamaz alan, oluşturucudan çıkış yaparken null olmayan bir değer içermelidir. Alanı null atanabilir olarak bildirmeyi düşünün. + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + Null atanamaz alan, oluşturucudan çıkış yaparken null olmayan bir değer içermelidir. Alanı null atanabilir olarak bildirmeyi düşünün. @@ -10973,7 +11013,7 @@ Yalnızca asenkron çağrının tamamlanmasını beklemek istemediğinizden ve Cannot specify a default value for a parameter collection - Parametre dizisi için varsayılan değer belirtilemez + Parametre koleksiyonu için varsayılan değer belirtilemez diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index fe0e988c8bca5..c05b02a950d19 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -337,6 +337,11 @@ 无法将方法组“{0}”转换为委托类型“{1}”(&M)。 + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. 无法推断委托类型。 @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + 无法从用法推断出方法“{0}”的类型参数,因为使用了动态类型参数,并且该方法具有非数组 params 集合参数。请尝试显式指定类型参数。 @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + 集合表达式类型“{0}”必须具有可以使用单个参数调用的实例或扩展方法 "Add"。 Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + 集合表达式类型必须具有可在不带参数的情况下调用的适用构造函数。 @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + 集合表达式目标“{0}”没有元素类型。 @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'.请改用关键字“params”。 @@ -1107,6 +1112,31 @@ 此命名空间中未启用“拦截器”实验性功能。请将“{0}”添加到项目。 + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' “UnmanagedCallersOnly”方法“{0}”无法实现类型“{2}”中的接口成员“{1}” @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + 修饰符不能放置在 using 声明上 @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + “{0}”的非数组 params 集合参数的扩展形式和正常形式之间的不明确性,唯一对应参数的类型为 “dynamic”。请考虑强制转换动态参数。 Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + 构造函数“{0}”的必需成员“{1}”未初始化。 An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + 表达式树不能包含非数组 params 集合参数的展开形式。 '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + “{0}”不包含适合的实例“Add”方法的定义 Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + 创建 params 集合“{0}”导致构造函数“{1}”调用的无限链。 Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + 非数组 params 集合类型必须拥有适用的构造函数,且该函数可以在无参数的情况下调用。 Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + 方法“{0}”的可见性不能小于具有 params 集合“{1}”的成员。 The params parameter must have a valid collection type - The params parameter must have a valid collection type + Params 参数必须具有有效的集合类型 @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + 隐式索引器初始值设定项 @@ -2314,7 +2344,7 @@ Lock object - Lock object + 锁定对象 @@ -2329,7 +2359,7 @@ params collections - params collections + Params 集合 @@ -2384,7 +2414,7 @@ string escape character - string escape character + 字符串转义字符 @@ -2457,6 +2487,16 @@ 指针 + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} 不是有效的 C# 分布操作 @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + 转换为不同类型的类型“System.Threading.Lock”的值将在“lock”语句中使用可能意外的基于监视器的锁定。 A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + 转换为不同类型的类型“System.Threading.Lock”的值将在“lock”语句中使用可能意外的基于监视器的锁定。 @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 具有非数组 params 集合参数的一个或多个构造函数重载可能仅适用于在动态调度期间不受支持的展开格式。 One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 具有非数组 params 集合参数的一个或多个构造函数重载可能仅适用于在动态调度期间不受支持的展开格式。 One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 具有非数组 params 集合参数的一个或多个索引器重载可能仅适用于在动态调度期间不受支持的展开格式。 One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 具有非数组 params 集合参数的一个或多个索引器重载可能仅适用于在动态调度期间不受支持的展开格式。 One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 具有非数组 params 集合参数的“{0}”方法的一个或多个方法重载可能仅适用于在动态调度期间不受支持的展开格式。 One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 具有非数组 params 集合参数的方法的一个或多个重载可能仅适用于在动态调度期间不受支持的扩展形式。 @@ -5109,13 +5149,13 @@ - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - 在退出构造函数时,不可为 null 的 {0}“{1}”必须包含非 null 值。请考虑将 {0} 声明为可以为 null。 + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + 在退出构造函数时,不可为 null 的 {0}“{1}”必须包含非 null 值。请考虑将 {0} 声明为可以为 null。 - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 @@ -10973,7 +11013,7 @@ You should consider suppressing the warning only if you're sure that you don't w Cannot specify a default value for a parameter collection - 无法为参数数组指定默认值 + 无法为参数集合指定默认值 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index f2f3842ce8508..8db26adf703cd 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -337,6 +337,11 @@ 無法將方法群組 '{0}' 轉換成委派類型 '{1}'(&M)。 + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. 無法推斷委派類型。 @@ -389,7 +394,7 @@ The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + 無法從使用方式推斷方法 '{0}' 的型別引數,因為使用了具有動態型別的引述,而且該方法具有非陣列參數集合參數。請嘗試明確指定型別引數。 @@ -453,13 +458,13 @@ - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. - Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'. + Collection expression type '{0}' must have an instance or extension method 'Add' that can be called with a single argument. + 集合運算式類型 '{0}' 必須有可以使用單一引數來呼叫的執行個體或延伸模組方法 'Add'。 Collection expression type must have an applicable constructor that can be called with no arguments. - Collection expression type must have an applicable constructor that can be called with no arguments. + 集合運算式型別必須具有可在不帶引數的情況下呼叫的適用建構函式。 @@ -469,7 +474,7 @@ Collection expression target '{0}' has no element type. - Collection expression target '{0}' has no element type. + 集合運算式目標 '{0}' 沒有元素型別。 @@ -629,7 +634,7 @@ Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. - Do not use 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'. Use the 'params' keyword instead. + 請勿使用 'System.ParamArrayAttribute'/'System.Runtime.CompilerServices.ParamCollectionAttribute'。請改用 'params' 關鍵字。 @@ -1107,6 +1112,31 @@ 未在此命名空間中啟用「攔截器」實驗功能。將 '{0}' 新增至您的專案。 + + The data argument to InterceptsLocationAttribute is not in the correct format. + The data argument to InterceptsLocationAttribute is not in the correct format. + + + + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + + + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation. + + + + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + Cannot intercept a call in file '{0}' because a matching file was not found in the compilation. + + + + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + Version '{0}' of the interceptors format is not supported. The latest supported version is '1'. + + 'UnmanagedCallersOnly' method '{0}' cannot implement interface member '{1}' in type '{2}' 'UnmanagedCallersOnly' 方法 '{0}' 無法在類型 '{2}' 中實作介面成員 '{1}' @@ -1429,7 +1459,7 @@ Modifiers cannot be placed on using declarations - Modifiers cannot be placed on using declarations + 修飾元不能置於 using 宣告中 @@ -1549,42 +1579,42 @@ Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - Ambiguity between expanded and normal forms of non-array params collection parameter of '{0}', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + 非陣列參數集合參數 '{0}' 的展開形式和正常形式之間存在歧義,唯一對應的引數型別為 'dynamic'。請考慮轉換動態引數。 Constructor '{0}' leaves required member '{1}' uninitialized. - Constructor '{0}' leaves required member '{1}' uninitialized. + 建構函式 '{0}' 未初始化必要的成員 '{1}'。 An expression tree may not contain an expanded form of non-array params collection parameter. - An expression tree may not contain an expanded form of non-array params collection parameter. + 運算式樹狀架構不能包含展開的非陣列參數集合參數形式。 '{0}' does not contain a definition for a suitable instance 'Add' method - '{0}' does not contain a definition for a suitable instance 'Add' method + '{0}' 不包含適當執行個體 'Add' 方法的定義 Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. - Creation of params collection '{0}' results in an infinite chain of invocation of constructor '{1}'. + 建立參數集合 '{0}' 產生無限鏈結的建構函式 '{1}' 叫用。 Non-array params collection type must have an applicable constructor that can be called with no arguments. - Non-array params collection type must have an applicable constructor that can be called with no arguments. + 非陣列參數集合型別必須具有可在不帶引數的情況下呼叫的適用建構函式。 Method '{0}' cannot be less visible than the member with params collection '{1}'. - Method '{0}' cannot be less visible than the member with params collection '{1}'. + 方法 '{0}' 不能小於具有參數集合 '{1}' 的成員。 The params parameter must have a valid collection type - The params parameter must have a valid collection type + params 參數必須具有有效的集合型別 @@ -2259,7 +2289,7 @@ implicit indexer initializer - implicit indexer initializer + 隱含索引子初始設定式 @@ -2314,7 +2344,7 @@ Lock object - Lock object + 鎖定物件 @@ -2329,7 +2359,7 @@ params collections - params collections + 參數集合 @@ -2384,7 +2414,7 @@ string escape character - string escape character + 字串逸出字元 @@ -2457,6 +2487,16 @@ 指標 + + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression at least {0} times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + + + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + + {0} is not a valid C# spread operation {0} 不是有效的 C# spread 作業 @@ -2674,12 +2714,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + 型別 'System.Threading.Lock' 轉換為不同型別的值,在 'lock' 陳述式中可能會使用非預期的監視器型鎖定。 A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + 型別 'System.Threading.Lock' 轉換為不同型別的值,在 'lock' 陳述式中可能會使用非預期的監視器型鎖定。 @@ -2704,32 +2744,32 @@ One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 一或多個具有非陣列參數集合參數的建構函式多載可能只適用於展開形式,但其在動態分派期間不支援。 One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 一或多個具有非陣列參數集合參數的建構函式多載可能只適用於展開形式,但其在動態分派期間不支援。 One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 一或多個具有非陣列參數集合參數的索引子多載可能只適用於展開形式,但其在動態分派期間不支援。 One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 一或多個具有非陣列參數集合參數的索引子多載可能只適用於展開形式,但其在動態分派期間不支援。 One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method '{0}' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 一或多個具有非陣列參數集合參數的方法 '{0}' 多載可能只適用於展開形式,但其在動態分派期間不支援。 One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - One or more overloads of method having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + 一或多個具有非陣列參數集合參數的方法多載可能只適用於展開形式,但其在動態分派期間不支援。 @@ -5109,13 +5149,13 @@ strument:TestCoverage 產生檢測要收集 - Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable. - 退出建構函式時,不可為 Null 的 {0} '{1}' 必須包含非 Null 值。請考慮將 {0} 宣告為可為 Null。 + Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the {0} as nullable. + 退出建構函式時,不可為 Null 的 {0} '{1}' 必須包含非 Null 值。請考慮將 {0} 宣告為可為 Null。 - Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - 退出建構函式時,不可為 Null 的欄位必須包含非 Null 值。請考慮宣告為可為 Null。 + Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + 退出建構函式時,不可為 Null 的欄位必須包含非 Null 值。請考慮宣告為可為 Null。 @@ -10973,7 +11013,7 @@ You should consider suppressing the warning only if you're sure that you don't w Cannot specify a default value for a parameter collection - 無法指定參數陣列的預設值 + 無法指定參數收集的預設值 diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTestBase.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTestBase.cs index 50e51c9e3b42d..e6ddad5f5b487 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTestBase.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTestBase.cs @@ -39,7 +39,7 @@ string getSdkDirectory(TempRoot temp) if (ExecutionConditionUtil.IsCoreClr) { var dir = temp.CreateDirectory(); - File.WriteAllBytes(Path.Combine(dir.Path, "mscorlib.dll"), Net461.References.mscorlib.ImageBytes); + File.WriteAllBytes(Path.Combine(dir.Path, "mscorlib.dll"), Net461.ReferenceInfos.mscorlib.ImageBytes); return dir.Path; } diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 63c7e533907ee..223d10d5847d3 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -210,6 +210,63 @@ class C Assert.Null(cmd.AnalyzerOptions); } + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/72657")] + public void AnalyzerConfig_DoubleSlash(bool doubleSlashAnalyzerConfig, bool doubleSlashSource) + { + var dir = Temp.CreateDirectory(); + var analyzer = new CompilationAnalyzerWithSeverity(DiagnosticSeverity.Warning, configurable: true); + var src = dir.CreateFile("Class1.cs").WriteAllText(""" + public class C + { + public void M() { } + } + """); + + // The analyzer should produce a warning. + var output = VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, analyzers: [analyzer], expectedWarningCount: 1); + AssertEx.Equal("Class1.cs(1,1): warning ID1000:", output.Trim()); + + // But not when this editorconfig is applied. + var editorconfig = dir.CreateFile(".editorconfig").WriteAllText(""" + root = true + + [*.cs] + dotnet_analyzer_diagnostic.severity = none + + generated_code = true + """); + var cmd = CreateCSharpCompiler( + [ + "/nologo", + "/preferreduilang:en", + "/t:library", + "/analyzerconfig:" + modifyPath(editorconfig.Path, doubleSlashAnalyzerConfig), + modifyPath(src.Path, doubleSlashSource), + ], + [analyzer]); + var outWriter = new StringWriter(CultureInfo.InvariantCulture); + var exitCode = cmd.Run(outWriter); + Assert.Equal(0, exitCode); + AssertEx.Equal("", outWriter.ToString()); + + static string modifyPath(string path, bool doubleSlash) + { + if (!doubleSlash) + { + return path; + } + + // Find the second-to-last slash. + char[] separators = ['/', '\\']; + var lastSlashIndex = path.LastIndexOfAny(separators); + lastSlashIndex = path.LastIndexOfAny(separators, lastSlashIndex - 1); + + // Duplicate that slash. + var lastSlash = path[lastSlashIndex]; + return path[0..lastSlashIndex] + lastSlash + path[lastSlashIndex..]; + } + } + [Fact] public void AnalyzerConfigWithOptions() { @@ -11986,7 +12043,7 @@ public sealed class DiagnosticDescriptor var minSystemCollectionsImmutableImage = CSharpCompilation.Create( "System.Collections.Immutable", new[] { SyntaxFactory.ParseSyntaxTree(minSystemCollectionsImmutableSource) }, - new MetadataReference[] { NetStandard13.SystemRuntime }, + new MetadataReference[] { NetStandard13.References.SystemRuntime }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, cryptoPublicKey: TestResources.TestKeys.PublicKey_b03f5f7f11d50a3a)).EmitToArray(); var minSystemCollectionsImmutableRef = MetadataReference.CreateFromImage(minSystemCollectionsImmutableImage); @@ -11996,7 +12053,7 @@ public sealed class DiagnosticDescriptor new[] { SyntaxFactory.ParseSyntaxTree(minCodeAnalysisSource) }, new MetadataReference[] { - NetStandard13.SystemRuntime, + NetStandard13.References.SystemRuntime, minSystemCollectionsImmutableRef }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, cryptoPublicKey: TestResources.TestKeys.PublicKey_31bf3856ad364e35)).EmitToArray(); @@ -12087,7 +12144,7 @@ public override void Initialize(AnalysisContext context) minCodeAnalysisRef, minSystemCollectionsImmutableRef }; - references = references.Concat(NetStandard13.All).ToArray(); + references = references.Concat(NetStandard13.References.All).ToArray(); var analyzerImage = CSharpCompilation.Create( analyzerAssemblyName, diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs index a49eaad3395f5..6c4a94e3e62c7 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs @@ -231,7 +231,7 @@ public static async MyTask M() // (20,16): warning CS8603: Possible null reference return. // return await G((string?)null); // 2 Diagnostic(ErrorCode.WRN_NullReferenceReturn, "await G((string?)null)").WithLocation(20, 16), - // (44,16): warning CS8618: Non-nullable field '_result' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (44,16): warning CS8618: Non-nullable field '_result' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // internal T _result; Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "_result").WithArguments("field", "_result").WithLocation(44, 16) ); diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs index 68a43d7eda5f7..8aa5ab4c25073 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs @@ -550,6 +550,62 @@ .maxstack 2 "); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")] + public void GotoInLambda_OutOfScope_Backward() + { + var code = """ + x: + System.Action a = () => + { + using System.IDisposable d = null; + goto x; + }; + """; + CreateCompilation(code).VerifyEmitDiagnostics( + // (1,1): warning CS0164: This label has not been referenced + // x: + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "x").WithLocation(1, 1), + // (5,5): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(5, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")] + public void GotoInLambda_OutOfScope_Forward() + { + var code = """ + System.Action a = () => + { + using System.IDisposable d = null; + goto x; + }; + x:; + """; + CreateCompilation(code).VerifyEmitDiagnostics( + // (4,5): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(4, 5), + // (6,1): warning CS0164: This label has not been referenced + // x:; + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "x").WithLocation(6, 1)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")] + public void GotoInLambda_NonExistent() + { + var code = """ + System.Action a = () => + { + using System.IDisposable d = null; + goto x; + }; + """; + CreateCompilation(code).VerifyEmitDiagnostics( + // (4,10): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "x").WithArguments("x").WithLocation(4, 10)); + } + // Definition same label in different lambdas [WorkItem(5991, "DevDiv_Projects/Roslyn")] [Fact] diff --git a/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests.cs b/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests.cs index cbf51c6c062c2..b25606241d2b2 100644 --- a/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests.cs @@ -651,7 +651,8 @@ class Program Assert.True(attributeData.HasErrors); Assert.Equal("AAttribute..ctor(params System.Int32[] args)", attributeData.AttributeConstructor.ToTestDisplayString()); Assert.Equal(1, attributeData.AttributeConstructor.ParameterCount); - Assert.Equal(new object[] { 1, 2, 3 }, attributeData.ConstructorArguments.Select(arg => arg.Value)); + Assert.Equal(TypedConstantKind.Array, attributeData.ConstructorArguments.Single().Kind); + Assert.Equal(new object[] { 1, 2, 3 }, attributeData.ConstructorArguments.Single().Values.Select(arg => arg.Value)); // `SourceAttributeData.GetAttributeArgumentSyntax` asserts in debug mode when the attributeData has errors, so we don't test it here. } @@ -1025,12 +1026,14 @@ class Program { } Assert.Equal(3, arguments0.Length); Assert.Equal(true, arguments0[0].Value); Assert.Equal(1, arguments0[1].Value); + Assert.Equal(TypedConstantKind.Array, arguments0[2].Kind); Assert.Empty(arguments0[2].Values); var arguments1 = attrs[1].ConstructorArguments.ToArray(); Assert.Equal(3, arguments1.Length); Assert.Equal(true, arguments1[0].Value); Assert.Equal(1, arguments1[1].Value); + Assert.Equal(TypedConstantKind.Array, arguments1[2].Kind); Assert.Equal("a", arguments1[2].Values.Single().Value); } diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs index 4119d42f5590f..d2f3bf455c812 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs @@ -5464,7 +5464,7 @@ static IEnumerable F() var v0 = CompileAndVerify(compilation0); v0.VerifyDiagnostics( - // (17,34): warning CS9230: 'yield return' should not be used in the body of a lock statement + // (17,34): warning CS9237: 'yield return' should not be used in the body of a lock statement // yield return 1; Diagnostic(ErrorCode.WRN_BadYieldInLock, "yield").WithLocation(17, 34)); var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); @@ -8832,7 +8832,7 @@ public class C } [Fact] - public void UpdateAsyncLambda() + public void AsyncLambda_Update() { var source0 = MarkedSource( @"using System; @@ -8942,6 +8942,78 @@ static void F() "<>u__2: System.Runtime.CompilerServices.TaskAwaiter"); } + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/72887")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72887")] + public void AsyncLambda_Delete() + { + using var _ = new EditAndContinueTest() + .AddBaseline(""" + using System.Threading.Tasks; + + class C + { + static void F() + { + Task.Run(async () => + { + await Task.FromResult(1); + }); + } + } + """) + .AddGeneration(""" + using System.Threading.Tasks; + + class C + { + static void F() + { + + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true), + ], + validator: v => + { + v.VerifySynthesizedMembers(); + + v.VerifyMethodDefNames("F", "b__1_0", "MoveNext"); + + v.VerifyEncLogDefinitions( + [ + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default) + ]); + + v.VerifyIL(""" + { + // Code size 2 (0x2) + .maxstack 8 + IL_0000: nop + IL_0001: ret + } + { + // Code size 11 (0xb) + .maxstack 8 + IL_0000: ldstr 0x70000005 + IL_0005: newobj 0x0A000017 + IL_000a: throw + } + { + // Code size 11 (0xb) + .maxstack 8 + IL_0000: ldstr 0x70000005 + IL_0005: newobj 0x0A000017 + IL_000a: throw + } + """); + }) + .Verify(); + } + [Fact, WorkItem(63294, "https://github.com/dotnet/roslyn/issues/63294")] public void LiftedClosure() { diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 370de764509c3..87f26b8e0675b 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -3779,12 +3779,6 @@ static void Main() 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), - // 1.cs(6,23): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'List.Add(int)'. - // List l = [1]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1]").WithArguments("object", "System.Collections.Generic.List.Add(int)").WithLocation(6, 23), - // 1.cs(6,23): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // List l = [1]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1]").WithArguments("1", "object", "int").WithLocation(6, 23), // 1.cs(7,16): error CS9174: Cannot initialize type 'IA' with a collection expression because the type is not constructible. // IA a = [2]; Diagnostic(ErrorCode.ERR_CollectionExpressionTargetTypeNotConstructible, "[2]").WithArguments("System.Collections.Generic.IA").WithLocation(7, 16), @@ -3849,12 +3843,6 @@ static void Main() 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), - // 1.cs(7,23): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'List.Add(int)'. - // List l = [1]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1]").WithArguments("object", "System.Collections.Generic.List.Add(int)").WithLocation(7, 23), - // 1.cs(7,23): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // List l = [1]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1]").WithArguments("1", "object", "int").WithLocation(7, 23), // 1.cs(8,29): error CS9174: Cannot initialize type 'IEquatable' with a collection expression because the type is not constructible. // IEquatable e = [2]; Diagnostic(ErrorCode.ERR_CollectionExpressionTargetTypeNotConstructible, "[2]").WithArguments("System.IEquatable").WithLocation(8, 29)); @@ -4573,48 +4561,48 @@ static void Main() """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (5,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments - // _ = (string)[]; - Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[]").WithArguments("string", "0").WithLocation(5, 21), - // (6,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments - // _ = (string)[default]; - Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[default]").WithArguments("string", "0").WithLocation(6, 21), - // (6,21): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) - // _ = (string)[default]; - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[default]").WithArguments("string", "Add").WithLocation(6, 21), - // (6,22): error CS8716: There is no target type for the default literal. - // _ = (string)[default]; - Diagnostic(ErrorCode.ERR_DefaultLiteralNoTargetType, "default").WithLocation(6, 22), - // (7,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments - // _ = (string)[null]; - Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[null]").WithArguments("string", "0").WithLocation(7, 21), - // (7,21): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) - // _ = (string)[null]; - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[null]").WithArguments("string", "Add").WithLocation(7, 21), - // (7,22): error CS0037: Cannot convert null to 'char' because it is a non-nullable value type - // _ = (string)[null]; - Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("char").WithLocation(7, 22), - // (8,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments - // _ = (string)['a']; - Diagnostic(ErrorCode.ERR_BadCtorArgCount, "['a']").WithArguments("string", "0").WithLocation(8, 21), - // (8,21): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) - // _ = (string)['a']; - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "['a']").WithArguments("string", "Add").WithLocation(8, 21), - // (9,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments - // _ = (string)[1]; - Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[1]").WithArguments("string", "0").WithLocation(9, 21), - // (9,21): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) - // _ = (string)[1]; - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[1]").WithArguments("string", "Add").WithLocation(9, 21), - // (9,22): error CS0029: Cannot implicitly convert type 'int' to 'char' - // _ = (string)[1]; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "char").WithLocation(9, 22), - // (10,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments - // _ = (string)[..""]; - Diagnostic(ErrorCode.ERR_BadCtorArgCount, @"[..""""]").WithArguments("string", "0").WithLocation(10, 21), - // (10,21): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) - // _ = (string)[..""]; - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, @"[..""""]").WithArguments("string", "Add").WithLocation(10, 21)); + // (5,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // _ = (string)[]; + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[]").WithArguments("string", "0").WithLocation(5, 21), + // (6,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // _ = (string)[default]; + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[default]").WithArguments("string", "0").WithLocation(6, 21), + // (6,21): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) + // _ = (string)[default]; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[default]").WithArguments("string", "Add").WithLocation(6, 21), + // (6,22): error CS8716: There is no target type for the default literal. + // _ = (string)[default]; + Diagnostic(ErrorCode.ERR_DefaultLiteralNoTargetType, "default").WithLocation(6, 22), + // (7,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // _ = (string)[null]; + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[null]").WithArguments("string", "0").WithLocation(7, 21), + // (7,21): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) + // _ = (string)[null]; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[null]").WithArguments("string", "Add").WithLocation(7, 21), + // (7,22): error CS0037: Cannot convert null to 'char' because it is a non-nullable value type + // _ = (string)[null]; + Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("char").WithLocation(7, 22), + // (8,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // _ = (string)['a']; + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "['a']").WithArguments("string", "0").WithLocation(8, 21), + // (8,21): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) + // _ = (string)['a']; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "['a']").WithArguments("string", "Add").WithLocation(8, 21), + // (9,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // _ = (string)[1]; + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[1]").WithArguments("string", "0").WithLocation(9, 21), + // (9,21): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) + // _ = (string)[1]; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[1]").WithArguments("string", "Add").WithLocation(9, 21), + // (9,22): error CS0029: Cannot implicitly convert type 'int' to 'char' + // _ = (string)[1]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "char").WithLocation(9, 22), + // (10,21): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // _ = (string)[..""]; + Diagnostic(ErrorCode.ERR_BadCtorArgCount, @"[..""""]").WithArguments("string", "0").WithLocation(10, 21), + // (10,21): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) + // _ = (string)[..""]; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, @"[..""""]").WithArguments("string", "Add").WithLocation(10, 21)); } [Fact] @@ -5330,13 +5318,7 @@ public void Add(int i) { } Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[]").WithArguments("C", "0").WithLocation(3, 5), // (4,5): error CS1729: 'C' does not contain a constructor that takes 0 arguments // c = [1, 2]; - Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[1, 2]").WithArguments("C", "0").WithLocation(4, 5), - // (4,5): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'C.Add(int)'. - // c = [1, 2]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, 2]").WithArguments("object", "C.Add(int)").WithLocation(4, 5), - // (4,5): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // c = [1, 2]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1, 2]").WithArguments("1", "object", "int").WithLocation(4, 5)); + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[1, 2]").WithArguments("C", "0").WithLocation(4, 5)); } [WorkItem("https://github.com/dotnet/roslyn/pull/71492")] @@ -5408,17 +5390,13 @@ static void Main() object o; c = [1, 2]; o = (C)[3, 4]; + c.Report(); + o.Report(); } } """; var comp = CreateCompilation(new[] { sourceA, sourceB2 }); - comp.VerifyEmitDiagnostics( - // 1.cs(7,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'C.Add(int)'. - // c = [1, 2]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, 2]").WithArguments("object", "C.Add(int)").WithLocation(7, 13), - // 1.cs(7,13): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // c = [1, 2]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1, 2]").WithArguments("1", "object", "int").WithLocation(7, 13)); + CompileAndVerify(new[] { sourceA, sourceB2, s_collectionExtensions }, expectedOutput: "[1, 2], [3, 4], "); } [Fact] @@ -5525,36 +5503,30 @@ public void CollectionInitializerType_08A() string source = """ using System; using System.Collections; + using System.Collections.Generic; struct S0 : IEnumerable { - public void Add(T t) { } - IEnumerator IEnumerable.GetEnumerator() => throw null; + private List _list; + public void Add(T t) { GetList().Add(t); } + IEnumerator IEnumerable.GetEnumerator() => GetList().GetEnumerator(); + private List GetList() => _list ??= new(); } class Program { - static void M0() + static void Main() { object o = (S0)[]; + o.Report(); o = (S0)[1, 2]; + o.Report(); S0 s = []; + s.Report(); s = [1, 2]; + s.Report(); } } """; - var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics( - // (13,22): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'S0.Add(int)'. - // o = (S0)[1, 2]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, 2]").WithArguments("object", "S0.Add(int)").WithLocation(13, 22), - // (13,22): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // o = (S0)[1, 2]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1, 2]").WithArguments("1", "object", "int").WithLocation(13, 22), - // (15,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'S0.Add(int)'. - // s = [1, 2]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, 2]").WithArguments("object", "S0.Add(int)").WithLocation(15, 13), - // (15,13): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // s = [1, 2]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1, 2]").WithArguments("1", "object", "int").WithLocation(15, 13)); + CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: "[], [1, 2], [], [1, 2], "); } [Fact] @@ -5778,12 +5750,9 @@ static void Main() """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (15,15): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'C.Add(IA)'. + // (15,36): error CS0121: The call is ambiguous between the following methods or properties: 'C.Add(IA)' and 'C.Add(IB)' // C c = [(IA)null, (IB)null, new AB()]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[(IA)null, (IB)null, new AB()]").WithArguments("object", "C.Add(IA)").WithLocation(15, 15), - // (15,15): error CS1503: Argument 1: cannot convert from 'object' to 'IA' - // C c = [(IA)null, (IB)null, new AB()]; - Diagnostic(ErrorCode.ERR_BadArgType, "[(IA)null, (IB)null, new AB()]").WithArguments("1", "object", "IA").WithLocation(15, 15)); + Diagnostic(ErrorCode.ERR_AmbigCall, "new AB()").WithArguments("C.Add(IA)", "C.Add(IB)").WithLocation(15, 36)); } [Fact] @@ -5813,12 +5782,9 @@ static void Main() """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (18,15): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'Extensions.Add(C, IA)'. - // C c = [(IA)null, (IB)null, new AB()]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[(IA)null, (IB)null, new AB()]").WithArguments("object", "Extensions.Add(C, IA)").WithLocation(18, 15), - // (18,15): error CS1503: Argument 2: cannot convert from 'object' to 'IA' + // (18,36): error CS0121: The call is ambiguous between the following methods or properties: 'Extensions.Add(C, IA)' and 'Extensions.Add(C, IB)' // C c = [(IA)null, (IB)null, new AB()]; - Diagnostic(ErrorCode.ERR_BadArgType, "[(IA)null, (IB)null, new AB()]").WithArguments("2", "object", "IA").WithLocation(18, 15)); + Diagnostic(ErrorCode.ERR_AmbigCall, "new AB()").WithArguments("Extensions.Add(C, IA)", "Extensions.Add(C, IB)").WithLocation(18, 36)); } [Fact] @@ -5843,9 +5809,9 @@ static void Main() """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (13,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'S.Add(int, int)'. + // (13,13): error CS9215: Collection expression type 'S' must have an instance or extension method 'Add' that can be called with a single argument. // s = [1, ..s]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, ..s]").WithArguments("object", "S.Add(int, int)").WithLocation(13, 13)); + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, ..s]").WithArguments("S").WithLocation(13, 13)); } [Fact] @@ -5872,9 +5838,9 @@ static void Main() """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (15,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'int'. The best overloaded method is 'S.Add(int, int)'. + // (15,13): error CS9215: Collection expression type 'S' must have an instance or extension method 'Add' that can be called with a single argument. // s = [1, ..s]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, ..s]").WithArguments("int", "S.Add(int, int)").WithLocation(15, 13)); + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, ..s]").WithArguments("S").WithLocation(15, 13)); } [Fact] @@ -5904,9 +5870,9 @@ static void Main() """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (18,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'int'. The best overloaded method is 'Extensions.Add(S, T, T)'. + // (18,13): error CS9215: Collection expression type 'S' must have an instance or extension method 'Add' that can be called with a single argument. // s = [1, ..s]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, ..s]").WithArguments("int", "Extensions.Add(S, T, T)").WithLocation(18, 13)); + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, ..s]").WithArguments("S").WithLocation(18, 13)); } [Fact] @@ -5926,18 +5892,13 @@ class Program static void Main() { C c = []; + c.Report(); c = [1, 2]; + c.Report(); } } """; - var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics( - // (14,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'C.Add(int, int)'. - // c = [1, 2]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, 2]").WithArguments("object", "C.Add(int, int)").WithLocation(14, 13), - // (14,13): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // c = [1, 2]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1, 2]").WithArguments("1", "object", "int").WithLocation(14, 13)); + CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: "[], [1, 2], "); } [Fact] @@ -5984,18 +5945,13 @@ class Program static void Main() { C c = []; + c.Report(); c = [1, 2]; + c.Report(); } } """; - var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics( - // (14,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'C.Add(int, params int[])'. - // c = [1, 2]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, 2]").WithArguments("object", "C.Add(int, params int[])").WithLocation(14, 13), - // (14,13): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // c = [1, 2]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1, 2]").WithArguments("1", "object", "int").WithLocation(14, 13)); + CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: "[], [1, 2], "); } [Fact] @@ -6112,18 +6068,12 @@ class Program """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (7,40): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'S.Add(T)'. - // static S Create(T t, U u) => [t, u]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[t, u]").WithArguments("object", "S.Add(T)").WithLocation(7, 40), - // (7,40): error CS1503: Argument 1: cannot convert from 'object' to 'T' - // static S Create(T t, U u) => [t, u]; - Diagnostic(ErrorCode.ERR_BadArgType, "[t, u]").WithArguments("1", "object", "T").WithLocation(7, 40), - // (11,46): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'S.Add(T)'. + // (11,50): error CS1950: The best overloaded Add method 'S.Add(T)' for the collection initializer has some invalid arguments // static S Create(T x, U y) => [x, y]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, y]").WithArguments("object", "S.Add(T)").WithLocation(11, 46), - // (11,46): error CS1503: Argument 1: cannot convert from 'object' to 'T' + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "y").WithArguments("S.Add(T)").WithLocation(11, 50), + // (11,50): error CS1503: Argument 1: cannot convert from 'U' to 'T' // static S Create(T x, U y) => [x, y]; - Diagnostic(ErrorCode.ERR_BadArgType, "[x, y]").WithArguments("1", "object", "T").WithLocation(11, 46)); + Diagnostic(ErrorCode.ERR_BadArgType, "y").WithArguments("1", "U", "T").WithLocation(11, 50)); } [Fact] @@ -6179,12 +6129,6 @@ class Program // (9,41): error CS0029: Cannot implicitly convert type 'T' to 'U' // static S Create(T t, U u) => [t, u]; Diagnostic(ErrorCode.ERR_NoImplicitConv, "t").WithArguments("T", "U").WithLocation(9, 41), - // (13,46): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'U'. The best overloaded method is 'S.Add(T)'. - // static S Create(T x, U y) => [x, y]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, y]").WithArguments("U", "S.Add(T)").WithLocation(13, 46), - // (13,46): error CS1503: Argument 1: cannot convert from 'U' to 'T' - // static S Create(T x, U y) => [x, y]; - Diagnostic(ErrorCode.ERR_BadArgType, "[x, y]").WithArguments("1", "U", "T").WithLocation(13, 46), // (13,47): error CS0029: Cannot implicitly convert type 'T' to 'U' // static S Create(T x, U y) => [x, y]; Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("T", "U").WithLocation(13, 47)); @@ -6695,9 +6639,9 @@ static void Main() // (16,42): error CS0411: The type arguments for method 'Extensions.Add(ref MyCollection, out T)' cannot be inferred from the usage. Try specifying the type arguments explicitly. // MyCollection x = new() { 1 }; Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "1").WithArguments("Extensions.Add(ref MyCollection, out T)").WithLocation(16, 42), - // (18,34): error CS1954: The best overloaded method match 'Extensions.Add(ref MyCollection, out object)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. + // (18,34): error CS1954: The best overloaded method match 'Extensions.Add(ref MyCollection, out T)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. // MyCollection z = [..x, ..y, 3]; - Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, "[..x, ..y, 3]").WithArguments("Extensions.Add(ref MyCollection, out object)").WithLocation(18, 34)); + Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, "[..x, ..y, 3]").WithArguments("Extensions.Add(ref MyCollection, out T)").WithLocation(18, 34)); } [Fact] @@ -6730,9 +6674,9 @@ static void Main() // (16,42): error CS0411: The type arguments for method 'Extensions.Add(ref MyCollection, ref T)' cannot be inferred from the usage. Try specifying the type arguments explicitly. // MyCollection x = new() { 1 }; Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "1").WithArguments("Extensions.Add(ref MyCollection, ref T)").WithLocation(16, 42), - // (18,34): error CS1954: The best overloaded method match 'Extensions.Add(ref MyCollection, ref object)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. + // (18,34): error CS1954: The best overloaded method match 'Extensions.Add(ref MyCollection, ref T)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. // MyCollection z = [..x, ..y, 3]; - Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, "[..x, ..y, 3]").WithArguments("Extensions.Add(ref MyCollection, ref object)").WithLocation(18, 34)); + Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, "[..x, ..y, 3]").WithArguments("Extensions.Add(ref MyCollection, ref T)").WithLocation(18, 34)); } [Fact] @@ -6837,12 +6781,12 @@ static void Main() // (20,21): error CS1503: Argument 2: cannot convert from 'int' to 'string' // y = new() { 3 }; Diagnostic(ErrorCode.ERR_BadArgType, "3").WithArguments("2", "int", "string").WithLocation(20, 21), - // (21,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'int'. The best overloaded method is 'Extensions.Add(ref MyCollection, string)'. + // (21,14): error CS1950: The best overloaded Add method 'Extensions.Add(ref MyCollection, string)' for the collection initializer has some invalid arguments // y = [4]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[4]").WithArguments("int", "Extensions.Add(ref MyCollection, string)").WithLocation(21, 13), - // (21,13): error CS1503: Argument 2: cannot convert from 'int' to 'string' + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "4").WithArguments("Extensions.Add(ref MyCollection, string)").WithLocation(21, 14), + // (21,14): error CS1503: Argument 2: cannot convert from 'int' to 'string' // y = [4]; - Diagnostic(ErrorCode.ERR_BadArgType, "[4]").WithArguments("2", "int", "string").WithLocation(21, 13)); + Diagnostic(ErrorCode.ERR_BadArgType, "4").WithArguments("2", "int", "string").WithLocation(21, 14)); } [Fact] @@ -6878,15 +6822,15 @@ static void Main() // (17,21): error CS1954: The best overloaded method match 'Extensions.Add(ref MyCollection, ref string)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. // x = new() { "1" }; Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, @"""1""").WithArguments("Extensions.Add(ref MyCollection, ref string)").WithLocation(17, 21), - // (18,13): error CS1954: The best overloaded method match 'Extensions.Add(ref MyCollection, ref string)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. + // (18,13): error CS1954: The best overloaded method match 'Extensions.Add(ref MyCollection, ref string)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. // x = ["2"]; - Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, @"[""2""]").WithArguments("Extensions.Add(ref MyCollection, ref string)").WithLocation(18, 13), + Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, @"[""2""]").WithArguments("Extensions.Add(ref MyCollection, ref string)").WithLocation(18, 13), // (20,21): error CS1954: The best overloaded method match 'Extensions.Add(ref MyCollection, ref string)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. // y = new() { 3 }; Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, "3").WithArguments("Extensions.Add(ref MyCollection, ref string)").WithLocation(20, 21), - // (21,13): error CS1954: The best overloaded method match 'Extensions.Add(ref MyCollection, ref string)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. + // (21,13): error CS1954: The best overloaded method match 'Extensions.Add(ref MyCollection, ref string)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. // y = [4]; - Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, "[4]").WithArguments("Extensions.Add(ref MyCollection, ref string)").WithLocation(21, 13)); + Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, "[4]").WithArguments("Extensions.Add(ref MyCollection, ref string)").WithLocation(21, 13)); } [Fact] @@ -8040,13 +7984,13 @@ static void Main() comp = CreateCompilation(new[] { sourceC, s_collectionExtensions }, references: new[] { refB }); comp.VerifyEmitDiagnostics( - // 0.cs(6,13): error CS0012: The type 'A1' is defined in an assembly that is not referenced. You must add a reference to assembly 'a897d975-a839-4fff-828b-deccf9495adc, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + // 0.cs(6,13): error CS0012: The type 'A1' is defined in an assembly that is not referenced. You must add a reference to assembly '6f8345f1-4f51-4a7a-a9f6-0597f76af3b9, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. // x = []; Diagnostic(ErrorCode.ERR_NoTypeDef, "[]").WithArguments("A1", $"{assemblyA}, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(6, 13), - // 0.cs(8,13): error CS0012: The type 'A1' is defined in an assembly that is not referenced. You must add a reference to assembly 'a897d975-a839-4fff-828b-deccf9495adc, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + // 0.cs(8,13): error CS0012: The type 'A1' is defined in an assembly that is not referenced. You must add a reference to assembly '6f8345f1-4f51-4a7a-a9f6-0597f76af3b9, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. // x = [1, 2]; Diagnostic(ErrorCode.ERR_NoTypeDef, "[1, 2]").WithArguments("A1", $"{assemblyA}, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(8, 13), - // 0.cs(13,13): error CS0012: The type 'A2' is defined in an assembly that is not referenced. You must add a reference to assembly 'a897d975-a839-4fff-828b-deccf9495adc, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + // 0.cs(13,14): error CS0012: The type 'A2' is defined in an assembly that is not referenced. You must add a reference to assembly '6f8345f1-4f51-4a7a-a9f6-0597f76af3b9, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. // y = [3, 4]; Diagnostic(ErrorCode.ERR_NoTypeDef, "[3, 4]").WithArguments("A2", $"{assemblyA}, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(13, 13)); } @@ -8147,12 +8091,12 @@ static void Main() """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (7,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'KeyValuePair'. The best overloaded method is 'Dictionary.Add(int, int)'. + // (7,13): error CS9215: Collection expression type 'Dictionary' must have an instance or extension method 'Add' that can be called with a single argument. // d = [default]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[default]").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.Dictionary.Add(int, int)").WithLocation(7, 13), - // (8,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'KeyValuePair'. The best overloaded method is 'Dictionary.Add(int, int)'. + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[default]").WithArguments("System.Collections.Generic.Dictionary").WithLocation(7, 13), + // (8,13): error CS9215: Collection expression type 'Dictionary' must have an instance or extension method 'Add' that can be called with a single argument. // d = [new KeyValuePair(1, 2)]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[new KeyValuePair(1, 2)]").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.Dictionary.Add(int, int)").WithLocation(8, 13), + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[new KeyValuePair(1, 2)]").WithArguments("System.Collections.Generic.Dictionary").WithLocation(8, 13), // (9,15): error CS1003: Syntax error, ',' expected // d = [3:4]; Diagnostic(ErrorCode.ERR_SyntaxError, ":").WithArguments(",").WithLocation(9, 15), @@ -9433,18 +9377,42 @@ static void Main() if (targetElementType == "int") { comp.VerifyEmitDiagnostics( - // 1.cs(10,26): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'MyCollection.Add(int)'. + // 1.cs(10,27): error CS1503: Argument 1: cannot convert from 'object' to 'int' // MyCollection c = [..d1, ..d2, ..e1, ..e2]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[..d1, ..d2, ..e1, ..e2]").WithArguments("object", "MyCollection.Add(int)").WithLocation(10, 26), - // 1.cs(10,26): error CS1503: Argument 1: cannot convert from 'object' to 'int' + Diagnostic(ErrorCode.ERR_BadArgType, "..d1").WithArguments("1", "object", "int").WithLocation(10, 27), + // 1.cs(10,29): error CS1950: The best overloaded Add method 'MyCollection.Add(int)' for the collection initializer has some invalid arguments // MyCollection c = [..d1, ..d2, ..e1, ..e2]; - Diagnostic(ErrorCode.ERR_BadArgType, "[..d1, ..d2, ..e1, ..e2]").WithArguments("1", "object", "int").WithLocation(10, 26), - // 1.cs(14,13): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'MyCollection.Add(int)'. + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "d1").WithArguments("MyCollection.Add(int)").WithLocation(10, 29), + // 1.cs(10,33): error CS1503: Argument 1: cannot convert from 'object' to 'int' + // MyCollection c = [..d1, ..d2, ..e1, ..e2]; + Diagnostic(ErrorCode.ERR_BadArgType, "..d2").WithArguments("1", "object", "int").WithLocation(10, 33), + // 1.cs(10,35): error CS1950: The best overloaded Add method 'MyCollection.Add(int)' for the collection initializer has some invalid arguments + // MyCollection c = [..d1, ..d2, ..e1, ..e2]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "d2").WithArguments("MyCollection.Add(int)").WithLocation(10, 35), + // 1.cs(10,39): error CS1503: Argument 1: cannot convert from 'object' to 'int' + // MyCollection c = [..d1, ..d2, ..e1, ..e2]; + Diagnostic(ErrorCode.ERR_BadArgType, "..e1").WithArguments("1", "object", "int").WithLocation(10, 39), + // 1.cs(10,41): error CS1950: The best overloaded Add method 'MyCollection.Add(int)' for the collection initializer has some invalid arguments + // MyCollection c = [..d1, ..d2, ..e1, ..e2]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "e1").WithArguments("MyCollection.Add(int)").WithLocation(10, 41), + // 1.cs(10,45): error CS1503: Argument 1: cannot convert from 'object' to 'int' + // MyCollection c = [..d1, ..d2, ..e1, ..e2]; + Diagnostic(ErrorCode.ERR_BadArgType, "..e2").WithArguments("1", "object", "int").WithLocation(10, 45), + // 1.cs(10,47): error CS1950: The best overloaded Add method 'MyCollection.Add(int)' for the collection initializer has some invalid arguments + // MyCollection c = [..d1, ..d2, ..e1, ..e2]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "e2").WithArguments("MyCollection.Add(int)").WithLocation(10, 47), + // 1.cs(14,14): error CS1503: Argument 1: cannot convert from 'object' to 'int' + // c = [..(dynamic)x, ..(IEnumerable)y]; + Diagnostic(ErrorCode.ERR_BadArgType, "..(dynamic)x").WithArguments("1", "object", "int").WithLocation(14, 14), + // 1.cs(14,16): error CS1950: The best overloaded Add method 'MyCollection.Add(int)' for the collection initializer has some invalid arguments + // c = [..(dynamic)x, ..(IEnumerable)y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "(dynamic)x").WithArguments("MyCollection.Add(int)").WithLocation(14, 16), + // 1.cs(14,28): error CS1503: Argument 1: cannot convert from 'object' to 'int' // c = [..(dynamic)x, ..(IEnumerable)y]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[..(dynamic)x, ..(IEnumerable)y]").WithArguments("object", "MyCollection.Add(int)").WithLocation(14, 13), - // 1.cs(14,13): error CS1503: Argument 1: cannot convert from 'object' to 'int' + Diagnostic(ErrorCode.ERR_BadArgType, "..(IEnumerable)y").WithArguments("1", "object", "int").WithLocation(14, 28), + // 1.cs(14,30): error CS1950: The best overloaded Add method 'MyCollection.Add(int)' for the collection initializer has some invalid arguments // c = [..(dynamic)x, ..(IEnumerable)y]; - Diagnostic(ErrorCode.ERR_BadArgType, "[..(dynamic)x, ..(IEnumerable)y]").WithArguments("1", "object", "int").WithLocation(14, 13)); + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "(IEnumerable)y").WithArguments("MyCollection.Add(int)").WithLocation(14, 30)); } else { @@ -13431,6 +13399,56 @@ private static void VerifyTypes(SemanticModel model, ExpressionSyntax expr, stri Assert.Equal(expectedConversionKind, conversion.Kind); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72541")] + public void NamedArgumentConversion() + { + var source = """ + #nullable enable + using System.Collections.Generic; + + static class C + { + static void Main() + { + C.M(y: [new D { }]); + } + static void M(string x, IReadOnlyList y) { } + } + + class D { } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,11): error CS7036: There is no argument given that corresponds to the required parameter 'x' of 'C.M(string, IReadOnlyList)' + // C.M(y: [new D { }]); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "M").WithArguments("x", "C.M(string, System.Collections.Generic.IReadOnlyList)").WithLocation(8, 11)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72541")] + public void NamedArgumentConversion_CollectionInitializer() + { + var source = """ + #nullable enable + using System.Collections.Generic; + + static class C + { + static void Main() + { + C.M(y: new() { new D() { } }); + } + static void M(string x, List y) { } + } + + class D { } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,11): error CS7036: There is no argument given that corresponds to the required parameter 'x' of 'C.M(string, List)' + // C.M(y: new() { new D() { } }); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "M").WithArguments("x", "C.M(string, System.Collections.Generic.List)").WithLocation(8, 11)); + } + [CombinatorialData] [Theory] public void CollectionBuilder_01(bool useCompilationReference) @@ -20434,6 +20452,34 @@ static void Main() CompileAndVerify(new[] { source, s_collectionExtensions }, verify: Verification.Skipped, expectedOutput: "[0, 1], "); } + [Fact] + public void RefStruct_04() + { + var source = """ + using System.Collections; + using System.Collections.Generic; + + dynamic d = null; + S s = [d]; + + ref struct S : IEnumerable + { + public IEnumerator GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; + public void Add(T t) => throw null; + } + """; + + CreateCompilation(source).VerifyDiagnostics( + // (5,7): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // S s = [d]; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "[d]").WithArguments("S").WithLocation(5, 7), + // (7,16): error CS8343: 'S': ref structs cannot implement interfaces + // ref struct S : IEnumerable + Diagnostic(ErrorCode.ERR_RefStructInterfaceImpl, "IEnumerable").WithArguments("S").WithLocation(7, 16) + ); + } + [CombinatorialData] [Theory] public void RefSafety_Return_01([CombinatorialValues(TargetFramework.Net70, TargetFramework.Net80)] TargetFramework targetFramework) @@ -25110,11 +25156,11 @@ .. GetConfig(), var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (4,52): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'KeyValuePair'. The best overloaded method is 'Dictionary.Add(string, object)'. + // (4,52): error CS9215: Collection expression type 'Dictionary' must have an instance or extension method 'Add' that can be called with a single argument. // Dictionary Config => /**/[ Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, @"[ .. GetConfig(), - ]").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.Dictionary.Add(string, object)").WithLocation(4, 52)); + ]").WithArguments("System.Collections.Generic.Dictionary").WithLocation(4, 52)); VerifyOperationTreeForTest(comp, """ @@ -25132,210 +25178,780 @@ .. GetConfig(), } [Fact] - public void Async_01() + public void IOperation_AmbiguousAdd_01() { - string source = """ - using System.Collections.Generic; - using System.Threading.Tasks; + string sourceA = """ + using System.Collections; + interface IA { } + interface IB { } + class MyCollection : IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() => throw null; + public void Add(IA a) => throw null; + public void Add(IB b) => throw null; + public void Add(object o) => throw null; + } + """; + string sourceB = """ + class C : IA, IB { } class Program { - static async Task Main() - { - (await CreateArray()).Report(); - (await CreateList()).Report(); - } - static async Task CreateArray() - { - return [await F(1), await F(2)]; - } - static async Task> CreateList() + static MyCollection Create(C x, C[] y) { - return [await F(3), await F(4)]; - } - static async Task F(int i) - { - await Task.Yield(); - return i; + return /**/[x, ..y]/**/; } } """; - CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: "[1, 2], [3, 4], "); + var comp = CreateCompilation([sourceB, sourceA]); + comp.VerifyEmitDiagnostics( + // (6,27): error CS0121: The call is ambiguous between the following methods or properties: 'MyCollection.Add(IA)' and 'MyCollection.Add(IB)' + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_AmbigCall, "x").WithArguments("MyCollection.Add(IA)", "MyCollection.Add(IB)").WithLocation(6, 27), + // (6,32): error CS0121: The call is ambiguous between the following methods or properties: 'MyCollection.Add(IA)' and 'MyCollection.Add(IB)' + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_AmbigCall, "y").WithArguments("MyCollection.Add(IA)", "MyCollection.Add(IB)").WithLocation(6, 32)); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[x, ..y]') + Elements(2): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C, IsInvalid) (Syntax: 'x') + ISpreadOperation (ElementType: C) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..y') + Operand: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: C[], IsInvalid) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + + var tree = comp.SyntaxTrees[0]; + var method = tree.GetRoot().DescendantNodes().OfType().Single(m => m.Identifier.Text == "Create"); + VerifyFlowGraph(comp, method, + """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection, IsInvalid, IsImplicit) (Syntax: '[x, ..y]') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (CollectionExpression) + Operand: + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[x, ..y]') + Elements(2): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C, IsInvalid) (Syntax: 'x') + ISpreadOperation (ElementType: C) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..y') + Operand: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: C[], IsInvalid) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """); } [Fact] - public void Async_02() + public void IOperation_AmbiguousAdd_02() { - string source = """ - using System.Collections.Generic; - using System.Threading.Tasks; + string sourceA = """ + using System.Collections; + interface IA { } + interface IB { } + class MyCollection : IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() => throw null; + public void Add(IA a) => throw null; + public void Add(IB b) => throw null; + } + static class Extensions + { + public static void Add(this MyCollection collection, object o) { } + } + """; + string sourceB = """ + class C : IA, IB { } class Program { - static async Task Main() - { - (await F2(F1())).Report(); - } - static async Task F1() - { - return [await F(1), await F(2)]; - } - static async Task F2(Task e) - { - return [3, .. await e, 4]; - } - static async Task F(T t) + static MyCollection Create(C x, C[] y) { - await Task.Yield(); - return t; + return /**/[x, ..y]/**/; } } """; - CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: "[3, 1, 2, 4], "); + var comp = CreateCompilation([sourceB, sourceA]); + comp.VerifyEmitDiagnostics( + // (6,27): error CS0121: The call is ambiguous between the following methods or properties: 'MyCollection.Add(IA)' and 'MyCollection.Add(IB)' + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_AmbigCall, "x").WithArguments("MyCollection.Add(IA)", "MyCollection.Add(IB)").WithLocation(6, 27), + // (6,32): error CS0121: The call is ambiguous between the following methods or properties: 'MyCollection.Add(IA)' and 'MyCollection.Add(IB)' + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_AmbigCall, "y").WithArguments("MyCollection.Add(IA)", "MyCollection.Add(IB)").WithLocation(6, 32)); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[x, ..y]') + Elements(2): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C, IsInvalid) (Syntax: 'x') + ISpreadOperation (ElementType: C) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..y') + Operand: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: C[], IsInvalid) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + + var tree = comp.SyntaxTrees[0]; + var method = tree.GetRoot().DescendantNodes().OfType().Single(m => m.Identifier.Text == "Create"); + VerifyFlowGraph(comp, method, + """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection, IsInvalid, IsImplicit) (Syntax: '[x, ..y]') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (CollectionExpression) + Operand: + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[x, ..y]') + Elements(2): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C, IsInvalid) (Syntax: 'x') + ISpreadOperation (ElementType: C) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..y') + Operand: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: C[], IsInvalid) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """); } [Fact] - public void PostfixIncrementDecrement() + public void IOperation_AmbiguousAdd_03() { - string source = """ + string sourceA = """ + using System.Collections; using System.Collections.Generic; - + class MyCollection : IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; + } + static class ExtensionsA + { + public static void Add(this MyCollection collection, string s) { } + } + static class ExtensionsB + { + public static void Add(this MyCollection collection, string s) { } + } + namespace N + { + static class ExtensionsC + { + public static void Add(this MyCollection collection, T t) { } + } + } + """; + string sourceB = """ + using N; class Program { - static void Main() + static MyCollection Create(string x, string[] y) { - []++; - []--; + return /**/[x, ..y]/**/; } } """; - CreateCompilation(source).VerifyEmitDiagnostics( - // (7,9): error CS1059: The operand of an increment or decrement operator must be a variable, property or indexer - // []++; - Diagnostic(ErrorCode.ERR_IncrementLvalueExpected, "[]").WithLocation(7, 9), - // (8,9): error CS1059: The operand of an increment or decrement operator must be a variable, property or indexer - // []--; - Diagnostic(ErrorCode.ERR_IncrementLvalueExpected, "[]").WithLocation(8, 9)); + var comp = CreateCompilation([sourceB, sourceA]); + comp.VerifyEmitDiagnostics( + // (6,27): error CS0121: The call is ambiguous between the following methods or properties: 'ExtensionsA.Add(MyCollection, string)' and 'ExtensionsB.Add(MyCollection, string)' + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_AmbigCall, "x").WithArguments("ExtensionsA.Add(MyCollection, string)", "ExtensionsB.Add(MyCollection, string)").WithLocation(6, 27), + // (6,32): error CS0121: The call is ambiguous between the following methods or properties: 'ExtensionsA.Add(MyCollection, string)' and 'ExtensionsB.Add(MyCollection, string)' + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_AmbigCall, "y").WithArguments("ExtensionsA.Add(MyCollection, string)", "ExtensionsB.Add(MyCollection, string)").WithLocation(6, 32)); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[x, ..y]') + Elements(2): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsInvalid) (Syntax: 'x') + ISpreadOperation (ElementType: System.String) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..y') + Operand: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: System.String[], IsInvalid) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + + var tree = comp.SyntaxTrees[0]; + var method = tree.GetRoot().DescendantNodes().OfType().Single(m => m.Identifier.Text == "Create"); + VerifyFlowGraph(comp, method, + """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection, IsInvalid, IsImplicit) (Syntax: '[x, ..y]') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (CollectionExpression) + Operand: + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[x, ..y]') + Elements(2): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsInvalid) (Syntax: 'x') + ISpreadOperation (ElementType: System.String) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..y') + Operand: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: System.String[], IsInvalid) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """); } [Fact] - public void PostfixPointerAccess() + public void IOperation_InvalidAdd_01() { - string source = """ - using System.Collections.Generic; - - class Program + string sourceA = """ + using System.Collections; + public class MyCollection : IEnumerable { - static void Main() - { - var v = []->Count; - } + IEnumerator IEnumerable.GetEnumerator() => throw null; + public void Add(string s) { } } """; - CreateCompilation(source).VerifyEmitDiagnostics( - // (7,17): error CS9503: There is no target type for the collection expression. - // var v = []->Count; - Diagnostic(ErrorCode.ERR_CollectionExpressionNoTargetType, "[]").WithLocation(7, 17)); - } - - [Fact] - public void LeftHandAssignment() - { - string source = """ - using System.Collections.Generic; - + string sourceB = """ class Program { - static void Main() + static MyCollection Create() { - [] = null; + return /**/[F1(), ..F2()]/**/; } + static int F1() => 1; + static int[] F2() => [2, 3]; } """; - CreateCompilation(source).VerifyEmitDiagnostics( - // (7,9): error CS0131: The left-hand side of an assignment must be a variable, property or indexer - // [] = null; - Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "[]").WithLocation(7, 9)); + var comp = CreateCompilation([sourceB, sourceA]); + comp.VerifyEmitDiagnostics( + // (5,27): error CS1950: The best overloaded Add method 'MyCollection.Add(string)' for the collection initializer has some invalid arguments + // return /**/[F1(), ..F2()]/**/; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "F1()").WithArguments("MyCollection.Add(string)").WithLocation(5, 27), + // (5,27): error CS1503: Argument 1: cannot convert from 'int' to 'string' + // return /**/[F1(), ..F2()]/**/; + Diagnostic(ErrorCode.ERR_BadArgType, "F1()").WithArguments("1", "int", "string").WithLocation(5, 27), + // (5,33): error CS1503: Argument 1: cannot convert from 'int' to 'string' + // return /**/[F1(), ..F2()]/**/; + Diagnostic(ErrorCode.ERR_BadArgType, "..F2()").WithArguments("1", "int", "string").WithLocation(5, 33), + // (5,35): error CS1950: The best overloaded Add method 'MyCollection.Add(string)' for the collection initializer has some invalid arguments + // return /**/[F1(), ..F2()]/**/; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "F2()").WithArguments("MyCollection.Add(string)").WithLocation(5, 35)); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[F1(), ..F2()]') + Elements(2): + IInvocationOperation (System.Int32 Program.F1()) (OperationKind.Invocation, Type: System.Int32, IsInvalid) (Syntax: 'F1()') + Instance Receiver: + null + Arguments(0) + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..F2()') + Operand: + IInvocationOperation (System.Int32[] Program.F2()) (OperationKind.Invocation, Type: System.Int32[], IsInvalid) (Syntax: 'F2()') + Instance Receiver: + null + Arguments(0) + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + + var tree = comp.SyntaxTrees[0]; + var method = tree.GetRoot().DescendantNodes().OfType().Single(m => m.Identifier.Text == "Create"); + VerifyFlowGraph(comp, method, + """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection, IsInvalid, IsImplicit) (Syntax: '[F1(), ..F2()]') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (CollectionExpression) + Operand: + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[F1(), ..F2()]') + Elements(2): + IInvocationOperation (System.Int32 Program.F1()) (OperationKind.Invocation, Type: System.Int32, IsInvalid) (Syntax: 'F1()') + Instance Receiver: + null + Arguments(0) + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..F2()') + Operand: + IInvocationOperation (System.Int32[] Program.F2()) (OperationKind.Invocation, Type: System.Int32[], IsInvalid) (Syntax: 'F2()') + Instance Receiver: + null + Arguments(0) + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """); } [Fact] - public void BinaryOperator() + public void IOperation_InvalidAdd_02() { - string source = """ - using System.Collections.Generic; - - class Program + string sourceA = """ + using System.Collections; + public class MyCollection : IEnumerable { - static void Main(List list) - { - [] + list; - } + IEnumerator IEnumerable.GetEnumerator() => throw null; + } + public static class Extensions + { + public static void Add(this MyCollection collection, string s) { } } """; - CreateCompilation(source).VerifyEmitDiagnostics( - // (7,9): error CS0019: Operator '+' cannot be applied to operands of type 'collection expressions' and 'List' - // [] + list; - Diagnostic(ErrorCode.ERR_BadBinaryOps, "[] + list").WithArguments("+", "collection expressions", "System.Collections.Generic.List").WithLocation(7, 9), - // (7,9): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement - // [] + list; - Diagnostic(ErrorCode.ERR_IllegalStatement, "[] + list").WithLocation(7, 9)); - } - - [Fact] - public void RangeOperator() - { - string source = """ - using System.Collections.Generic; - + string sourceB = """ class Program { - static void Main(List list) + static MyCollection Create() { - []..; + return /**/[F1(), ..F2()]/**/; } + static int F1() => 1; + static int[] F2() => [2, 3]; } """; - CreateCompilationWithIndexAndRangeAndSpan(source).VerifyEmitDiagnostics( - // (7,9): error CS9500: Cannot initialize type 'Index' with a collection expression because the type is not constructible. - // []..; - Diagnostic(ErrorCode.ERR_CollectionExpressionTargetTypeNotConstructible, "[]").WithArguments("System.Index").WithLocation(7, 9), - // (7,9): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement - // []..; - Diagnostic(ErrorCode.ERR_IllegalStatement, "[]..").WithLocation(7, 9)); + var comp = CreateCompilation([sourceB, sourceA]); + comp.VerifyEmitDiagnostics( + // (5,27): error CS1950: The best overloaded Add method 'Extensions.Add(MyCollection, string)' for the collection initializer has some invalid arguments + // return /**/[F1(), ..F2()]/**/; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "F1()").WithArguments("Extensions.Add(MyCollection, string)").WithLocation(5, 27), + // (5,27): error CS1503: Argument 2: cannot convert from 'int' to 'string' + // return /**/[F1(), ..F2()]/**/; + Diagnostic(ErrorCode.ERR_BadArgType, "F1()").WithArguments("2", "int", "string").WithLocation(5, 27), + // (5,33): error CS1503: Argument 2: cannot convert from 'int' to 'string' + // return /**/[F1(), ..F2()]/**/; + Diagnostic(ErrorCode.ERR_BadArgType, "..F2()").WithArguments("2", "int", "string").WithLocation(5, 33), + // (5,35): error CS1950: The best overloaded Add method 'Extensions.Add(MyCollection, string)' for the collection initializer has some invalid arguments + // return /**/[F1(), ..F2()]/**/; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "F2()").WithArguments("Extensions.Add(MyCollection, string)").WithLocation(5, 35)); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[F1(), ..F2()]') + Elements(2): + IInvocationOperation (System.Int32 Program.F1()) (OperationKind.Invocation, Type: System.Int32, IsInvalid) (Syntax: 'F1()') + Instance Receiver: + null + Arguments(0) + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..F2()') + Operand: + IInvocationOperation (System.Int32[] Program.F2()) (OperationKind.Invocation, Type: System.Int32[], IsInvalid) (Syntax: 'F2()') + Instance Receiver: + null + Arguments(0) + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + + var tree = comp.SyntaxTrees[0]; + var method = tree.GetRoot().DescendantNodes().OfType().Single(m => m.Identifier.Text == "Create"); + VerifyFlowGraph(comp, method, + """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection, IsInvalid, IsImplicit) (Syntax: '[F1(), ..F2()]') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (CollectionExpression) + Operand: + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[F1(), ..F2()]') + Elements(2): + IInvocationOperation (System.Int32 Program.F1()) (OperationKind.Invocation, Type: System.Int32, IsInvalid) (Syntax: 'F1()') + Instance Receiver: + null + Arguments(0) + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..F2()') + Operand: + IInvocationOperation (System.Int32[] Program.F2()) (OperationKind.Invocation, Type: System.Int32[], IsInvalid) (Syntax: 'F2()') + Instance Receiver: + null + Arguments(0) + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """); } [Fact] - public void TopLevelSwitchExpression() + public void IOperation_InvalidAdd_03() { - string source = """ - using System.Collections.Generic; - + string sourceA = """ + using System.Collections; + public class MyCollection : IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() => throw null; + } + public static class Extensions + { + public static void Add(this MyCollection collection, params string[] args) { } + } + """; + string sourceB = """ class Program { - static void Main(List list) + static MyCollection Create(int x, int[] y) { - [] switch { null => 0 }; + return /**/[x, ..y]/**/; } } """; - CreateCompilation(source).VerifyEmitDiagnostics( - // (7,9): error CS9503: There is no target type for the collection expression. - // [] switch - Diagnostic(ErrorCode.ERR_CollectionExpressionNoTargetType, "[]").WithLocation(7, 9), - // (7,9): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement - // [] switch - Diagnostic(ErrorCode.ERR_IllegalStatement, "[] switch { null => 0 }").WithLocation(7, 9)); + var comp = CreateCompilation([sourceB, sourceA]); + comp.VerifyEmitDiagnostics( + // (5,27): error CS1950: The best overloaded Add method 'Extensions.Add(MyCollection, params string[])' for the collection initializer has some invalid arguments + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "x").WithArguments("Extensions.Add(MyCollection, params string[])").WithLocation(5, 27), + // (5,27): error CS1503: Argument 2: cannot convert from 'int' to 'string' + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("2", "int", "string").WithLocation(5, 27), + // (5,30): error CS1503: Argument 2: cannot convert from 'int' to 'string' + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_BadArgType, "..y").WithArguments("2", "int", "string").WithLocation(5, 30), + // (5,32): error CS1950: The best overloaded Add method 'Extensions.Add(MyCollection, params string[])' for the collection initializer has some invalid arguments + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "y").WithArguments("Extensions.Add(MyCollection, params string[])").WithLocation(5, 32)); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[x, ..y]') + Elements(2): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..y') + Operand: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: System.Int32[], IsInvalid) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + + var tree = comp.SyntaxTrees[0]; + var method = tree.GetRoot().DescendantNodes().OfType().Single(m => m.Identifier.Text == "Create"); + VerifyFlowGraph(comp, method, + """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection, IsInvalid, IsImplicit) (Syntax: '[x, ..y]') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (CollectionExpression) + Operand: + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[x, ..y]') + Elements(2): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..y') + Operand: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: System.Int32[], IsInvalid) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """); } [Fact] - public void TopLevelWithExpression() + public void IOperation_InvalidAdd_04() { - string source = """ - using System.Collections.Generic; - + string sourceA = """ + using System; + using System.Collections; + public class MyCollection : IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() => throw null; + public Action Add; + } + """; + string sourceB = """ class Program { - static void Main(List list) + static MyCollection Create(int x, int[] y) { - [] with { Count = 1, }; + return /**/[x, ..y]/**/; + } + } + """; + var comp = CreateCompilation([sourceB, sourceA]); + comp.VerifyEmitDiagnostics( + // (5,26): error CS0118: 'Add' is a field but is used like a method + // return /**/[x, ..y]/**/; + Diagnostic(ErrorCode.ERR_BadSKknown, "[x, ..y]").WithArguments("Add", "field", "method").WithLocation(5, 26)); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[x, ..y]') + Elements(2): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..y') + Operand: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: System.Int32[], IsInvalid) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (NoConversion) + """); + + var tree = comp.SyntaxTrees[0]; + var method = tree.GetRoot().DescendantNodes().OfType().Single(m => m.Identifier.Text == "Create"); + VerifyFlowGraph(comp, method, + """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection, IsInvalid, IsImplicit) (Syntax: '[x, ..y]') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (NoConversion) + Operand: + ICollectionExpressionOperation (2 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: MyCollection, IsInvalid) (Syntax: '[x, ..y]') + Elements(2): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '..y') + Operand: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: System.Int32[], IsInvalid) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (NoConversion) + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """); + } + + [Fact] + public void Async_01() + { + string source = """ + using System.Collections.Generic; + using System.Threading.Tasks; + class Program + { + static async Task Main() + { + (await CreateArray()).Report(); + (await CreateList()).Report(); + } + static async Task CreateArray() + { + return [await F(1), await F(2)]; + } + static async Task> CreateList() + { + return [await F(3), await F(4)]; + } + static async Task F(int i) + { + await Task.Yield(); + return i; + } + } + """; + CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: "[1, 2], [3, 4], "); + } + + [Fact] + public void Async_02() + { + string source = """ + using System.Collections.Generic; + using System.Threading.Tasks; + class Program + { + static async Task Main() + { + (await F2(F1())).Report(); + } + static async Task F1() + { + return [await F(1), await F(2)]; + } + static async Task F2(Task e) + { + return [3, .. await e, 4]; + } + static async Task F(T t) + { + await Task.Yield(); + return t; + } + } + """; + CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: "[3, 1, 2, 4], "); + } + + [Fact] + public void PostfixIncrementDecrement() + { + string source = """ + using System.Collections.Generic; + + class Program + { + static void Main() + { + []++; + []--; + } + } + """; + CreateCompilation(source).VerifyEmitDiagnostics( + // (7,9): error CS1059: The operand of an increment or decrement operator must be a variable, property or indexer + // []++; + Diagnostic(ErrorCode.ERR_IncrementLvalueExpected, "[]").WithLocation(7, 9), + // (8,9): error CS1059: The operand of an increment or decrement operator must be a variable, property or indexer + // []--; + Diagnostic(ErrorCode.ERR_IncrementLvalueExpected, "[]").WithLocation(8, 9)); + } + + [Fact] + public void PostfixPointerAccess() + { + string source = """ + using System.Collections.Generic; + + class Program + { + static void Main() + { + var v = []->Count; + } + } + """; + CreateCompilation(source).VerifyEmitDiagnostics( + // (7,17): error CS9503: There is no target type for the collection expression. + // var v = []->Count; + Diagnostic(ErrorCode.ERR_CollectionExpressionNoTargetType, "[]").WithLocation(7, 17)); + } + + [Fact] + public void LeftHandAssignment() + { + string source = """ + using System.Collections.Generic; + + class Program + { + static void Main() + { + [] = null; + } + } + """; + CreateCompilation(source).VerifyEmitDiagnostics( + // (7,9): error CS0131: The left-hand side of an assignment must be a variable, property or indexer + // [] = null; + Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "[]").WithLocation(7, 9)); + } + + [Fact] + public void BinaryOperator() + { + string source = """ + using System.Collections.Generic; + + class Program + { + static void Main(List list) + { + [] + list; + } + } + """; + CreateCompilation(source).VerifyEmitDiagnostics( + // (7,9): error CS0019: Operator '+' cannot be applied to operands of type 'collection expressions' and 'List' + // [] + list; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "[] + list").WithArguments("+", "collection expressions", "System.Collections.Generic.List").WithLocation(7, 9), + // (7,9): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement + // [] + list; + Diagnostic(ErrorCode.ERR_IllegalStatement, "[] + list").WithLocation(7, 9)); + } + + [Fact] + public void RangeOperator() + { + string source = """ + using System.Collections.Generic; + + class Program + { + static void Main(List list) + { + []..; + } + } + """; + CreateCompilationWithIndexAndRangeAndSpan(source).VerifyEmitDiagnostics( + // (7,9): error CS9500: Cannot initialize type 'Index' with a collection expression because the type is not constructible. + // []..; + Diagnostic(ErrorCode.ERR_CollectionExpressionTargetTypeNotConstructible, "[]").WithArguments("System.Index").WithLocation(7, 9), + // (7,9): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement + // []..; + Diagnostic(ErrorCode.ERR_IllegalStatement, "[]..").WithLocation(7, 9)); + } + + [Fact] + public void TopLevelSwitchExpression() + { + string source = """ + using System.Collections.Generic; + + class Program + { + static void Main(List list) + { + [] switch { null => 0 }; + } + } + """; + CreateCompilation(source).VerifyEmitDiagnostics( + // (7,9): error CS9503: There is no target type for the collection expression. + // [] switch + Diagnostic(ErrorCode.ERR_CollectionExpressionNoTargetType, "[]").WithLocation(7, 9), + // (7,9): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement + // [] switch + Diagnostic(ErrorCode.ERR_IllegalStatement, "[] switch { null => 0 }").WithLocation(7, 9)); + } + + [Fact] + public void TopLevelWithExpression() + { + string source = """ + using System.Collections.Generic; + + class Program + { + static void Main(List list) + { + [] with { Count = 1, }; } } """; @@ -25985,13 +26601,7 @@ public static void M(int[] i) { } var comp = CreateCompilation(source).VerifyEmitDiagnostics( // (3,7): error CS9214: Collection expression type must have an applicable constructor that can be called with no arguments. // C x = [1]; // 1 - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingConstructor, "[1]").WithLocation(3, 7), - // (3,7): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'C.Add(int)'. - // C x = [1]; // 1 - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1]").WithArguments("object", "C.Add(int)").WithLocation(3, 7), - // (3,7): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // C x = [1]; // 1 - Diagnostic(ErrorCode.ERR_BadArgType, "[1]").WithArguments("1", "object", "int").WithLocation(3, 7) + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingConstructor, "[1]").WithLocation(3, 7) ); var tree = comp.SyntaxTrees.First(); @@ -26034,13 +26644,7 @@ public static void M(int[] i) { } var comp = CreateCompilation(source).VerifyEmitDiagnostics( // (4,7): error CS9214: Collection expression type must have an applicable constructor that can be called with no arguments. // C x = [..values]; // 1 - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingConstructor, "[..values]").WithLocation(4, 7), - // (4,7): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'C.Add(int)'. - // C x = [..values]; // 1 - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[..values]").WithArguments("object", "C.Add(int)").WithLocation(4, 7), - // (4,7): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // C x = [..values]; // 1 - Diagnostic(ErrorCode.ERR_BadArgType, "[..values]").WithArguments("1", "object", "int").WithLocation(4, 7) + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingConstructor, "[..values]").WithLocation(4, 7) ); var tree = comp.SyntaxTrees.First(); @@ -26081,12 +26685,6 @@ public void Add(int i) { } // (4,7): error CS9214: Collection expression type must have an applicable constructor that can be called with no arguments. // C x = [1]; // 1 Diagnostic(ErrorCode.ERR_CollectionExpressionMissingConstructor, "[1]").WithLocation(4, 7), - // (4,7): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'string'. The best overloaded method is 'C.Add(int)'. - // C x = [1]; // 1 - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1]").WithArguments("string", "C.Add(int)").WithLocation(4, 7), - // (4,7): error CS1503: Argument 1: cannot convert from 'string' to 'int' - // C x = [1]; // 1 - Diagnostic(ErrorCode.ERR_BadArgType, "[1]").WithArguments("1", "string", "int").WithLocation(4, 7), // (4,8): error CS0029: Cannot implicitly convert type 'int' to 'string' // C x = [1]; // 1 Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "string").WithLocation(4, 8) @@ -26126,12 +26724,6 @@ public static void M(int[] i) { } // (5,7): error CS9214: Collection expression type must have an applicable constructor that can be called with no arguments. // C x = [..values]; // 1 Diagnostic(ErrorCode.ERR_CollectionExpressionMissingConstructor, "[..values]").WithLocation(5, 7), - // (5,7): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'string'. The best overloaded method is 'C.Add(int)'. - // C x = [..values]; // 1 - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[..values]").WithArguments("string", "C.Add(int)").WithLocation(5, 7), - // (5,7): error CS1503: Argument 1: cannot convert from 'string' to 'int' - // C x = [..values]; // 1 - Diagnostic(ErrorCode.ERR_BadArgType, "[..values]").WithArguments("1", "string", "int").WithLocation(5, 7), // (5,10): error CS0029: Cannot implicitly convert type 'int' to 'string' // C x = [..values]; // 1 Diagnostic(ErrorCode.ERR_NoImplicitConv, "values").WithArguments("int", "string").WithLocation(5, 10) @@ -26824,23 +27416,14 @@ class MyCollection2 : IEnumerable where TAdd : TElemen } """; - var comp = CreateCompilation(new[] { source, s_collectionExtensions }, targetFramework: TargetFramework.Net70); - comp.VerifyEmitDiagnostics( - // 0.cs(4,24): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'MyCollection1.Add(int)'. - // MyCollection1 x = [1, 2, 3]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, 2, 3]").WithArguments("object", "MyCollection1.Add(int)").WithLocation(4, 24), - // 0.cs(4,24): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // MyCollection1 x = [1, 2, 3]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1, 2, 3]").WithArguments("1", "object", "int").WithLocation(4, 24), - // 0.cs(6,32): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'MyCollection2.Add(int)'. - // MyCollection2 y = [1, 2, 3]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, 2, 3]").WithArguments("object", "MyCollection2.Add(int)").WithLocation(6, 32), - // 0.cs(6,32): error CS1503: Argument 1: cannot convert from 'object' to 'int' - // MyCollection2 y = [1, 2, 3]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1, 2, 3]").WithArguments("1", "object", "int").WithLocation(6, 32)); - } - - [Fact] + CompileAndVerify( + new[] { source, s_collectionExtensions }, + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3], ")); + } + + [Fact] public void GenericIEnumerable_NarrowerConversionToAdd_WiderElements() { string source = """ @@ -26860,19 +27443,19 @@ class MyCollection2 : IEnumerable where TAdd : TElemen var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); comp.VerifyEmitDiagnostics( - // (4,32): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'MyCollection2.Add(int)'. + // (4,33): error CS1950: The best overloaded Add method 'MyCollection2.Add(int)' for the collection initializer has some invalid arguments // MyCollection2 y = [new object()]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[new object()]").WithArguments("object", "MyCollection2.Add(int)").WithLocation(4, 32), - // (4,32): error CS1503: Argument 1: cannot convert from 'object' to 'int' + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "new object()").WithArguments("MyCollection2.Add(int)").WithLocation(4, 33), + // (4,33): error CS1503: Argument 1: cannot convert from 'object' to 'int' // MyCollection2 y = [new object()]; - Diagnostic(ErrorCode.ERR_BadArgType, "[new object()]").WithArguments("1", "object", "int").WithLocation(4, 32) + Diagnostic(ErrorCode.ERR_BadArgType, "new object()").WithArguments("1", "object", "int").WithLocation(4, 33) ); } [Fact] public void GenericIEnumerable_DifferentConversionToAdd() { - // For purpose of conversion, we rely on conversion from numeric literal to uint (from IEnumerable) + // For purpose of conversion, we rely on the existence of an Add method. // But for purpose of construction, we rely on conversion from numeric literal to sbyte (from Add(sbyte)) string source = """ using System.Collections; @@ -26890,21 +27473,18 @@ class MyCollection : IEnumerable } """; - var comp = CreateCompilation(new[] { source, s_collectionExtensions }, targetFramework: TargetFramework.Net70); - comp.VerifyEmitDiagnostics( - // 0.cs(4,18): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'uint'. The best overloaded method is 'MyCollection.Add(sbyte)'. - // MyCollection x = [1, 2, 3]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, 2, 3]").WithArguments("uint", "MyCollection.Add(sbyte)").WithLocation(4, 18), - // 0.cs(4,18): error CS1503: Argument 1: cannot convert from 'uint' to 'sbyte' - // MyCollection x = [1, 2, 3]; - Diagnostic(ErrorCode.ERR_BadArgType, "[1, 2, 3]").WithArguments("1", "uint", "sbyte").WithLocation(4, 18)); + CompileAndVerify( + new[] { source, s_collectionExtensions }, + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); } [Fact] public void GenericIEnumerable_NoConversionToAdd() { - // For purpose of conversion, we rely on conversion from numeric literal to uint (from IEnumerable) - // But for purpose of construction, we rely on conversion from numeric literal to sbyte (from Add(sbyte)) + // For purpose of conversion, we rely on the existence of an Add method. + // But for purpose of construction, there is no conversion from uint to sbyte (from Add(sbyte)) string source = """ using System.Collections; using System.Collections.Generic; @@ -26922,12 +27502,12 @@ class MyCollection : IEnumerable var comp = CreateCompilation(new[] { source, s_collectionExtensions }, targetFramework: TargetFramework.Net70); comp.VerifyEmitDiagnostics( - // 0.cs(4,18): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'uint'. The best overloaded method is 'MyCollection.Add(sbyte)'. + // 0.cs(4,19): error CS1950: The best overloaded Add method 'MyCollection.Add(sbyte)' for the collection initializer has some invalid arguments // MyCollection x = [uint.MaxValue]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[uint.MaxValue]").WithArguments("uint", "MyCollection.Add(sbyte)").WithLocation(4, 18), - // 0.cs(4,18): error CS1503: Argument 1: cannot convert from 'uint' to 'sbyte' + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "uint.MaxValue").WithArguments("MyCollection.Add(sbyte)").WithLocation(4, 19), + // 0.cs(4,19): error CS1503: Argument 1: cannot convert from 'uint' to 'sbyte' // MyCollection x = [uint.MaxValue]; - Diagnostic(ErrorCode.ERR_BadArgType, "[uint.MaxValue]").WithArguments("1", "uint", "sbyte").WithLocation(4, 18) + Diagnostic(ErrorCode.ERR_BadArgType, "uint.MaxValue").WithArguments("1", "uint", "sbyte").WithLocation(4, 19) ); } @@ -27144,12 +27724,9 @@ class Collection : IEnumerable """; CreateCompilation(source).VerifyEmitDiagnostics( - // (4,16): error CS9215: Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type 'object'. The best overloaded method is 'Collection.Add(I1)'. - // Collection c = [new C()]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[new C()]").WithArguments("object", "Collection.Add(I1)").WithLocation(4, 16), - // (4,16): error CS1503: Argument 1: cannot convert from 'object' to 'I1' + // (4,17): error CS0121: The call is ambiguous between the following methods or properties: 'Collection.Add(I1)' and 'Collection.Add(I2)' // Collection c = [new C()]; - Diagnostic(ErrorCode.ERR_BadArgType, "[new C()]").WithArguments("1", "object", "I1").WithLocation(4, 16)); + Diagnostic(ErrorCode.ERR_AmbigCall, "new C()").WithArguments("Collection.Add(I1)", "Collection.Add(I2)").WithLocation(4, 17)); } [Fact] @@ -33407,893 +33984,4220 @@ public void TargetTypedElement_PublicAPI_List() using System.Collections.Generic; class C { - static void Main() + static void Main() + { + List items = [new()]; + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var info = model.GetSymbolInfo(node); + Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); + + model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ + ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Generic.List..ctor()) (OperationKind.CollectionExpression, Type: System.Collections.Generic.List) (Syntax: '[new()]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') + Arguments(0) + Initializer: + null + """); + } + + [Fact] + public void TargetTypedElement_PublicAPI_Array() + { + var source = """ + class C + { + static void Main() + { + object[] items = [new()]; + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var info = model.GetSymbolInfo(node); + Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); + + model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ + ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Object[]) (Syntax: '[new()]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') + Arguments(0) + Initializer: + null + """); + } + + [Fact] + public void TargetTypedElement_PublicAPI_Span() + { + var source = """ + using System; + + class C + { + static void Main() + { + Span items = [new()]; + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var info = model.GetSymbolInfo(node); + Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); + + model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ + ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Span) (Syntax: '[new()]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') + Arguments(0) + Initializer: + null + """); + } + + [Fact] + public void TargetTypedElement_PublicAPI_ReadOnlySpan() + { + var source = """ + using System; + + class C + { + static void Main() + { + ReadOnlySpan items = [new()]; + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var info = model.GetSymbolInfo(node); + Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); + + model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ + ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.ReadOnlySpan) (Syntax: '[new()]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') + Arguments(0) + Initializer: + null + """); + } + + [Fact] + public void TargetTypedElement_PublicAPI_ImmutableArray() + { + var source = """ + using System.Collections.Immutable; + + class C + { + static void Main() + { + ImmutableArray items = [new()]; + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var info = model.GetSymbolInfo(node); + Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); + + model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ + ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Immutable.ImmutableArray System.Collections.Immutable.ImmutableArray.Create(System.ReadOnlySpan items)) (OperationKind.CollectionExpression, Type: System.Collections.Immutable.ImmutableArray) (Syntax: '[new()]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') + Arguments(0) + Initializer: + null + """); + } + + [Fact] + public void TargetTypedElement_PublicAPI_IEnumerableT() + { + var source = """ + using System.Collections.Generic; + + class C + { + static void Main() + { + IEnumerable items = [new()]; + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var info = model.GetSymbolInfo(node); + Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); + + model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ + ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Collections.Generic.IEnumerable) (Syntax: '[new()]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') + Arguments(0) + Initializer: + null + """); + } + + [Fact] + public void TargetTypedElement_PublicAPI_ImplementsIEnumerable() + { + var source = """ + using System.Collections; + + class C + { + static void Main() + { + MyCollection items = [new()]; + } + } + + class MyCollection : IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() => throw null!; + public void Add(object obj) { } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var info = model.GetSymbolInfo(node); + Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); + + model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ + ICollectionExpressionOperation (1 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[new()]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') + Arguments(0) + Initializer: + null + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_01() + { + string source = """ + using System; + using System.Collections.Generic; + static class Extensions + { + public static void Add(this ICollection collection, params T[] elements) + { + foreach (T element in elements) + collection.Add(element); + } + } + class Program + { + static Dictionary CreateDictionary(ICollection> collection) + { + return /**/[..collection]/**/; + } + static void Main() + { + var v = new KeyValuePair[] { new("a", "b"), new("c", "d") }; + var d = CreateDictionary(v); + foreach (var kvp in d) + Console.Write("({0}, {1}), ", kvp.Key, kvp.Value); + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "(a, b), (c, d), "); + + verifier.VerifyIL("Extensions.Add(this System.Collections.Generic.ICollection, params T[])", """ + { + // Code size 32 (0x20) + .maxstack 2 + .locals init (T[] V_0, + int V_1, + T V_2) //element + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + IL_0004: br.s IL_0019 + IL_0006: ldloc.0 + IL_0007: ldloc.1 + IL_0008: ldelem "T" + IL_000d: stloc.2 + IL_000e: ldarg.0 + IL_000f: ldloc.2 + IL_0010: callvirt "void System.Collections.Generic.ICollection.Add(T)" + IL_0015: ldloc.1 + IL_0016: ldc.i4.1 + IL_0017: add + IL_0018: stloc.1 + IL_0019: ldloc.1 + IL_001a: ldloc.0 + IL_001b: ldlen + IL_001c: conv.i4 + IL_001d: blt.s IL_0006 + IL_001f: ret + } + """); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Generic.Dictionary..ctor()) (OperationKind.CollectionExpression, Type: System.Collections.Generic.Dictionary) (Syntax: '[..collection]') + Elements(1): + ISpreadOperation (ElementType: System.Collections.Generic.KeyValuePair) (OperationKind.Spread, Type: null) (Syntax: '..collection') + Operand: + IParameterReferenceOperation: collection (OperationKind.ParameterReference, Type: System.Collections.Generic.ICollection>) (Syntax: 'collection') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsCollection_01() + { + string source = """ + using System; + using System.Collections.Generic; + static class Extensions + { + public static void Add(this ICollection collection, params IEnumerable elements) + { + foreach (T element in elements) + collection.Add(element); + } + } + class Program + { + static Dictionary CreateDictionary(ICollection> collection) + { + return /**/[..collection]/**/; + } + static void Main() + { + var v = new KeyValuePair[] { new("a", "b"), new("c", "d") }; + var d = CreateDictionary(v); + foreach (var kvp in d) + Console.Write("({0}, {1}), ", kvp.Key, kvp.Value); + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: "(a, b), (c, d), ").VerifyDiagnostics(); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Generic.Dictionary..ctor()) (OperationKind.CollectionExpression, Type: System.Collections.Generic.Dictionary) (Syntax: '[..collection]') + Elements(1): + ISpreadOperation (ElementType: System.Collections.Generic.KeyValuePair) (OperationKind.Spread, Type: null) (Syntax: '..collection') + Operand: + IParameterReferenceOperation: collection (OperationKind.ParameterReference, Type: System.Collections.Generic.ICollection>) (Syntax: 'collection') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_02() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public void Add(params T[] x) => _list.AddRange(x); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + class Program + { + static void Main() + { + int x = 1; + MyCollection y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_03() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + internal void __AddRange(T[] x) { _list.AddRange(x); } + } + static class Extensions + { + public static void Add(this MyCollection c, params T[] x) { c.__AddRange(x); } + } + class Program + { + static void Main() + { + int x = 1; + MyCollection y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + """); + } + + [Fact] + public void Add_ParamsArray_04() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public void Add(T x, params T[] y) => _list.Add(x); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + class Program + { + static void Main() + { + int x = 1; + MyCollection y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + """); + } + + [Fact] + public void Add_ParamsArray_05() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + internal void __Add(T x) { _list.Add(x); } + } + static class Extensions + { + public static void Add(this MyCollection c, T x, params T[] y) { c.__Add(x); } + } + class Program + { + static void Main() + { + int x = 1; + MyCollection y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_06() + { + string sourceA = """ + using System; + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public void Add(params MyCollection[] x) + { + Console.Write("Add: "); + x.Report(); + Console.WriteLine(); + _list.AddRange(x); + } + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + MyCollection x = []; + MyCollection[] y = []; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: """ + Add: [[]], + [[]], + """); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'x') + ISpreadOperation (ElementType: MyCollection) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection[]) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + + string sourceB2 = """ + class Program + { + static void Main() + { + MyCollection x = /**/[[]]/**/; + x.Report(); + } + } + """; + + comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: """ + Add: [], + [], + """); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (1 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[[]]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection[], IsImplicit) (Syntax: '[]') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ICollectionExpressionOperation (0 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: MyCollection[]) (Syntax: '[]') + Elements(0) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_07() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + struct MyCollection : IEnumerable + { + private List _list; + public void Add(params MyCollection?[] x) => GetList().AddRange(x); + public IEnumerator GetEnumerator() => GetList().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + private List GetList() => _list ??= new(); + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + MyCollection x = []; + MyCollection[] y = []; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[[]], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection?, IsImplicit) (Syntax: 'x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'x') + ISpreadOperation (ElementType: MyCollection) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection[]) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (ImplicitNullable) + """); + + string sourceB2 = """ + class Program + { + static void Main() + { + MyCollection? x = /**/[[]]/**/; + x.Value.Report(); + } + } + """; + + comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (1 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[[]]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection?[], IsImplicit) (Syntax: '[]') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ICollectionExpressionOperation (0 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: MyCollection?[]) (Syntax: '[]') + Elements(0) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_08() + { + string sourceA = """ + using System; + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public void Add(params object[] x) + { + Console.Write("Add: "); + foreach (var i in x) + Console.Write("{0}, ", i); + Console.WriteLine(); + _list.AddRange(x); + } + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + object x = 1; + object[] y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: """ + Add: 1, + Add: 2, + Add: 3, + [1, 2, 3], + """); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Object) (Syntax: 'x') + ISpreadOperation (ElementType: System.Object) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Object[]) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + + string sourceB2 = """ + class Program + { + static void Main() + { + object[] x = [1]; + object[][] y = [[2, 3]]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: """ + Add: 1, + Add: 2, 3, + [1, 2, 3], + """); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Object[]) (Syntax: 'x') + ISpreadOperation (ElementType: System.Object[]) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Object[][]) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_09() + { + string sourceA1 = """ + public abstract class MyCollectionBase + { + public abstract void Add(object[] x); + } + """; + string assemblyName = GetUniqueName(); + var comp = CreateCompilation(new AssemblyIdentity(assemblyName, new Version(1, 0, 0, 0)), sourceA1, references: TargetFrameworkUtil.StandardReferences); + var refA1 = comp.EmitToImageReference(); + + string sourceB = """ + using System.Collections; + using System.Collections.Generic; + public class MyCollection : MyCollectionBase, IEnumerable + { + private List _list = new(); + public override void Add(object[] x) => _list.AddRange(x); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + """; + comp = CreateCompilation(sourceB, references: [refA1]); + var refB = comp.EmitToImageReference(); + + string sourceA2 = """ + public abstract class MyCollectionBase + { + public abstract void Add(params object[] x); + } + """; + comp = CreateCompilation(new AssemblyIdentity(assemblyName, new Version(2, 0, 0, 0)), sourceA2, references: TargetFrameworkUtil.StandardReferences); + var refA2 = comp.EmitToImageReference(); + + string sourceC = """ + class Program + { + static void Main() + { + object x = 1; + object[] y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + comp = CreateCompilation([sourceC, s_collectionExtensions], references: [refA2, refB], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Object) (Syntax: 'x') + ISpreadOperation (ElementType: System.Object) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Object[]) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72098")] + [Fact] + public void AddMethod_Derived_01() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + + class Element { } + + class ElementCollection : IEnumerable + { + private readonly List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + public void Add(Element element) { _list.Add(element); } + } + + class Program + { + static void Main() + { + ElementCollection c = [new Element(), null]; + c.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[Element, null], "); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72098")] + [Fact] + public void AddMethod_Derived_02() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + + class Base { } + class Element : Base { } + + class ElementCollection : IEnumerable + { + private readonly List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Add(Element element) { _list.Add(element); } + } + + class Program + { + static void Main() + { + ElementCollection c = [new Element(), null]; + c.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[Element, null], "); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/71240")] + [Fact] + public void AddMethod_Derived_03() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + + class Sample : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(T t) { if (t is object[] o) _list.Add(o); } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + Sample s = [["a"], ["b"], ["c"]]; + s.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB1, s_collectionExtensions], expectedOutput: "[[a], [b], [c]], "); + + string sourceB2 = """ + class Program + { + static void Main() + { + Sample s = ["a", null]; + } + } + """; + var comp = CreateCompilation([sourceA, sourceB2]); + comp.VerifyEmitDiagnostics( + // (5,29): error CS0029: Cannot implicitly convert type 'string' to 'object[]' + // Sample s = ["a", null]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""a""").WithArguments("string", "object[]").WithLocation(5, 29)); + } + + [Fact] + public void AddMethod_Accessibility_01() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + partial class MyCollection : IEnumerable + { + private readonly List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + private void Add(int i) { _list.Add(i is T t ? t : default); } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + } + } + """; + var comp = CreateCompilation([sourceA, sourceB1]); + comp.VerifyEmitDiagnostics( + // (7,34): error CS0122: 'MyCollection.Add(int)' is inaccessible due to its protection level + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadAccess, "[x, ..y]").WithArguments("MyCollection.Add(int)").WithLocation(7, 34)); + + string sourceB2 = """ + partial class MyCollection + { + public static void Run() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + class Program + { + static void Main() + { + MyCollection.Run(); + } + } + """; + CompileAndVerify([sourceA, sourceB2, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + } + + [Fact] + public void AddMethod_Accessibility_02() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + public class MyCollection : IEnumerable + { + private readonly List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + internal void __AddInternal(int i) { _list.Add(i is T t ? t : default); } + } + internal static class Extensions + { + public static void Add(this MyCollection c, int i) { c.__AddInternal(i); } + } + """; + + string sourceB = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + + var comp = CreateCompilation(sourceA); + var refA = comp.ToMetadataReference(); + + comp = CreateCompilation([sourceB, s_collectionExtensions], references: [refA]); + comp.VerifyEmitDiagnostics( + // (7,34): error CS1061: 'MyCollection' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'MyCollection' could be found (are you missing a using directive or an assembly reference?) + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[x, ..y]").WithArguments("MyCollection", "Add").WithLocation(7, 34)); + } + + [Fact] + public void AddMethod_Accessibility_03() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollectionBase : IEnumerable + { + private readonly List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + protected void Add(T t) { _list.Add(t); } + } + class MyCollection : MyCollectionBase + { + internal void __AddInternal(T t) { Add(t); } + } + """; + string sourceB = """ + static class Extensions + { + public static void Add(this MyCollection c, T t) { c.__AddInternal(t); } + } + """; + string sourceC = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + + CompileAndVerify([sourceA, sourceB, sourceC, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + + var comp = CreateCompilation([sourceA, sourceC, s_collectionExtensions]); + comp.VerifyEmitDiagnostics( + // (7,34): error CS0122: 'MyCollectionBase.Add(object)' is inaccessible due to its protection level + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadAccess, "[x, ..y]").WithArguments("MyCollectionBase.Add(object)").WithLocation(7, 34)); + } + + [Fact] + public void AddMethod_Overloads() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Add(string s) { _list.Add(s is T t ? t : default); } + public void Add(int i) { _list.Add(i is T t ? t : default); } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB1, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + + string sourceB2 = """ + class Program + { + static void Main() + { + string x = "1"; + string[] y = ["2", "3"]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB2, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + + string sourceB3 = """ + class Program + { + static void Main() + { + int? x = 1; + int?[] y = [2, 3]; + MyCollection z = [x, ..y]; + } + } + """; + var comp = CreateCompilation([sourceA, sourceB3]); + comp.VerifyEmitDiagnostics( + // (7,33): error CS1950: The best overloaded Add method 'MyCollection.Add(string)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "x").WithArguments("MyCollection.Add(string)").WithLocation(7, 33), + // (7,33): error CS1503: Argument 1: cannot convert from 'int?' to 'string' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "int?", "string").WithLocation(7, 33), + // (7,36): error CS1503: Argument 1: cannot convert from 'int?' to 'string' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "..y").WithArguments("1", "int?", "string").WithLocation(7, 36), + // (7,38): error CS1950: The best overloaded Add method 'MyCollection.Add(string)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "y").WithArguments("MyCollection.Add(string)").WithLocation(7, 38)); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref readonly")] + public void AddMethod_ByRef_01(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add({{refKind}} T t) { _list.Add(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Theory] + [InlineData("out")] + [InlineData("ref")] + public void AddMethod_ByRef_02(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add({{refKind}} T t) { t = default; _list.Add(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (15,32): error CS1954: The best overloaded method match 'MyCollection.Add(ref int?)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, "[x, ..y, null]").WithArguments("MyCollection.Add(" + refKind + " int?)").WithLocation(15, 32)); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref readonly")] + public void AddMethod_ByRef_03(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + interface IA { } + interface IB { } + interface IC { } + class C : IA, IB, IC + { + private readonly int _i; + public C(int i) { _i = i; } + public override string ToString() => _i.ToString(); + public static implicit operator C(int i) => new(i); + } + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(out IA a) => throw null; + public void Add(ref IB b) => throw null; + public void Add({{refKind}} IC c) { _list.Add(c); } + } + class Program + { + static void Main() + { + C x = 1; + C[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref readonly")] + public void AddMethod_ByRef_Extension_01(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, {{refKind}} T t) { collection.__AddInternal(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Theory] + [InlineData("out")] + [InlineData("ref")] + public void AddMethod_ByRef_Extension_02(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, {{refKind}} T t) { t = default; collection.__AddInternal(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (19,32): error CS1954: The best overloaded method match 'Extensions.Add(MyCollection, ref T)' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_InitializerAddHasParamModifiers, "[x, ..y, null]").WithArguments("Extensions.Add(MyCollection, " + refKind + " T)").WithLocation(19, 32)); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref readonly")] + public void AddMethod_ByRef_Extension_03(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + ref struct R { } + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + public void Add(R r) => throw null; + } + static class Extensions + { + public static void Add(this MyCollection collection, {{refKind}} T t) { collection.__AddInternal(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Theory] + [InlineData("out")] + [InlineData("ref")] + public void AddMethod_ByRef_Extension_04(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + ref struct R { } + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + public void Add(R r) => throw null; + } + static class Extensions + { + public static void Add(this MyCollection collection, {{refKind}} T t) { t = default; collection.__AddInternal(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (21,33): error CS1950: The best overloaded Add method 'MyCollection.Add(R)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "x").WithArguments("MyCollection.Add(R)").WithLocation(21, 33), + // (21,33): error CS1503: Argument 1: cannot convert from 'int' to 'R' + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "int", "R").WithLocation(21, 33), + // (21,36): error CS1503: Argument 1: cannot convert from 'int' to 'R' + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_BadArgType, "..y").WithArguments("1", "int", "R").WithLocation(21, 36), + // (21,38): error CS1950: The best overloaded Add method 'MyCollection.Add(R)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "y").WithArguments("MyCollection.Add(R)").WithLocation(21, 38), + // (21,41): error CS1950: The best overloaded Add method 'MyCollection.Add(R)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "null").WithArguments("MyCollection.Add(R)").WithLocation(21, 41), + // (21,41): error CS1503: Argument 1: cannot convert from '' to 'R' + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_BadArgType, "null").WithArguments("1", "", "R").WithLocation(21, 41)); + } + + [Theory] + [InlineData("out")] + [InlineData("ref")] + public void AddMethod_ByRef_Extension_05(string refKind) + { + string source = $$""" + using N; + using System.Collections; + using System.Collections.Generic; + ref struct R { } + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + public void Add(R r) => throw null; + } + static class Extensions + { + public static void Add(this MyCollection collection, {{refKind}} T t) { t = default; collection.__AddInternal(t); } + } + namespace N + { + static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref readonly")] + public void AddMethod_ByRef_Extension_06(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + interface IA { } + interface IB { } + interface IC { } + class C : IA, IB, IC + { + private readonly int _i; + public C(int i) { _i = i; } + public override string ToString() => _i.ToString(); + public static implicit operator C(int i) => new(i); + } + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(out IA a) => throw null; + public void Add(ref IB b) => throw null; + internal void __AddInternal(IC c) { _list.Add(c); } + } + static class Extensions + { + public static void Add(this MyCollection collection, {{refKind}} IC c) { collection.__AddInternal(c); } + } + class Program + { + static void Main() + { + C x = 1; + C[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref readonly")] + public void AddMethod_ByRef_Extension_07(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + using N; + interface IA { } + interface IB { } + interface IC { } + class C : IA, IB, IC + { + private readonly int _i; + public C(int i) { _i = i; } + public override string ToString() => _i.ToString(); + public static implicit operator C(int i) => new(i); + } + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(IC c) { _list.Add(c); } + } + static class Extensions + { + public static void Add(this MyCollection collection, out IA a) => throw null; + public static void Add(this MyCollection collection, ref IB b) => throw null; + } + namespace N + { + static class Extensions + { + public static void Add(this MyCollection collection, {{refKind}} IC c) { collection.__AddInternal(c); } + } + } + class Program + { + static void Main() + { + C x = 1; + C[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Fact] + public void AddMethod_01() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add() { _list.Add(default); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (15,32): error CS1501: No overload for method 'Add' takes 1 arguments + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_BadArgCount, "[x, ..y, null]").WithArguments("Add", "1").WithLocation(15, 32)); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref readonly")] + public void AddMethod_02A(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(T t, {{refKind}} int x = 1, {{refKind}} int y = 2) { _list.Add(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Fact] + public void AddMethod_02B() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + abstract class MyCollectionBase + { + public abstract void Add(T t, int x = 1); + } + class MyCollection : MyCollectionBase, IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public override void Add(T t, int x) { _list.Add(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + MyCollection w = new() { x }; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (19,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y]").WithArguments("MyCollection").WithLocation(19, 32), + // (20,40): error CS7036: There is no argument given that corresponds to the required parameter 'x' of 'MyCollection.Add(int?, int)' + // MyCollection w = new() { x }; + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "x").WithArguments("x", "MyCollection.Add(int?, int)").WithLocation(20, 40)); + } + + [Fact] + public void AddMethod_02C() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + abstract class MyCollectionBase + { + public abstract void Add(T t, int x); + } + class MyCollection : MyCollectionBase, IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public override void Add(T t, int x = 1) { _list.Add(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + MyCollection w = new() { x }; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Theory] + [InlineData("")] + [InlineData("in ")] + [InlineData("ref readonly ")] + public void AddMethod_03A(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(T t, {{refKind}} int x, {{refKind}} int y = 2) { _list.Add(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + } + } + """; + var comp = CreateCompilation(source); + if (refKind == "ref readonly ") + { + comp.VerifyEmitDiagnostics( + // (7,69): warning CS9200: A default value is specified for 'ref readonly' parameter 'y', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public void Add(T t, ref readonly int x, ref readonly int y = 2) { _list.Add(t); } + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "2").WithArguments("y").WithLocation(7, 69), + // (15,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y, null]").WithArguments("MyCollection").WithLocation(15, 32)); + } + else + { + comp.VerifyEmitDiagnostics( + // (15,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y, null]").WithArguments("MyCollection").WithLocation(15, 32)); + } + } + + [Theory] + [CombinatorialData] + public void AddMethod_03B(bool useOut) + { + // public struct MyCollection : IEnumerable + // { + // IEnumerator IEnumerable.GetEnumerator() => null; + // public void Add(T t, ref int index = 0) => throw null; + // } + string sourceA = $$""" + .class public sealed MyCollection`1 + extends [mscorlib]System.ValueType + implements [mscorlib]System.Collections.IEnumerable + { + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } + .method private instance class [mscorlib]System.Collections.IEnumerator GetEnumerator() + { + .override [mscorlib]System.Collections.IEnumerable::GetEnumerator + ldnull + ret + } + .method public instance void Add(!T t, {{(useOut ? "[out]" : "")}} [opt] int32& index) + { + .param [2] = int32(0x00000000) + ldnull + ret + } + } + """; + var refA = CompileIL(sourceA); + + string sourceB = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + MyCollection w = new() { x }; + } + } + """; + var comp = CreateCompilation(sourceB, references: [refA]); + comp.VerifyEmitDiagnostics( + // (7,31): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y]").WithArguments("MyCollection").WithLocation(7, 31), + // (8,39): error CS7036: There is no argument given that corresponds to the required parameter 'index' of 'MyCollection.Add(int, ref int)' + // MyCollection w = new() { x }; + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "x").WithArguments("index", $"MyCollection.Add(int, {(useOut ? "out" : "ref")} int)").WithLocation(8, 39)); + } + + [Fact] + public void AddMethod_04A() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + abstract class MyCollectionBase + { + public abstract void Add(params T[] args); + } + class MyCollection : MyCollectionBase, IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public override void Add(T[] args) + { + if (args is null) return; + _list.AddRange(args); + } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB1, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + + string sourceB2 = """ + class Program + { + static void Main() + { + MyCollection x = new() { (int?)null, null }; + MyCollection y = [(int?)null, null]; + x.Report(); + y.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB2, s_collectionExtensions], expectedOutput: "[null], [null], "); + } + + [Fact] + public void AddMethod_04B() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + abstract class MyCollectionBase + { + public abstract void Add(T[] args); + } + class MyCollection : MyCollectionBase, IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public override void Add(params T[] args) + { + if (args is null) return; + _list.AddRange(args); + } + } + """; + + string sourceB = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + } + } + """; + var comp = CreateCompilation([sourceA, sourceB]); + comp.VerifyEmitDiagnostics( + // (7,33): error CS1950: The best overloaded Add method 'MyCollection.Add(int?[])' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "x").WithArguments("MyCollection.Add(int?[])").WithLocation(7, 33), + // (7,33): error CS1503: Argument 1: cannot convert from 'int' to 'int?[]' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "int", "int?[]").WithLocation(7, 33), + // (7,36): error CS1503: Argument 1: cannot convert from 'int' to 'int?[]' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "..y").WithArguments("1", "int", "int?[]").WithLocation(7, 36), + // (7,38): error CS1950: The best overloaded Add method 'MyCollection.Add(int?[])' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "y").WithArguments("MyCollection.Add(int?[])").WithLocation(7, 38)); + } + + [Fact] + public void AddMethod_05A() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + abstract class MyCollectionBase + { + public abstract void Add(T x, params T[] y); + } + class MyCollection : MyCollectionBase, IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public override void Add(T x, T[] y) { _list.Add(x); _list.AddRange(y); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Fact] + public void AddMethod_05B() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + abstract class MyCollectionBase + { + public abstract void Add(T x, T[] y); + } + class MyCollection : MyCollectionBase, IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public override void Add(T x, params T[] y) { _list.Add(x); _list.AddRange(y); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (19,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y, null]").WithArguments("MyCollection").WithLocation(19, 32)); + } + + [Fact] + public void AddMethod_06A() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + abstract class MyCollectionBase + { + public abstract void Add(T x, T y = default, params T[] z); + } + class MyCollection : MyCollectionBase, IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public override void Add(T x, T y, T[] z) { _list.Add(x); _list.Add(y); _list.AddRange(z); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + MyCollection w = new() { x }; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (19,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y]").WithArguments("MyCollection").WithLocation(19, 32), + // (20,40): error CS7036: There is no argument given that corresponds to the required parameter 'y' of 'MyCollection.Add(int?, int?, params int?[])' + // MyCollection w = new() { x }; + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "x").WithArguments("y", "MyCollection.Add(int?, int?, params int?[])").WithLocation(20, 40)); + } + + [Fact] + public void AddMethod_06B() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + abstract class MyCollectionBase + { + public abstract void Add(T x, T y, T[] z); + } + class MyCollection : MyCollectionBase, IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public override void Add(T x, T y = default, params T[] z) { _list.Add(x); _list.Add(y); _list.AddRange(z); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + MyCollection w = new() { x }; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (19,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y]").WithArguments("MyCollection").WithLocation(19, 32), + // (20,40): error CS7036: There is no argument given that corresponds to the required parameter 'z' of 'MyCollection.Add(int?, int?, int?[])' + // MyCollection w = new() { x }; + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "x").WithArguments("z", "MyCollection.Add(int?, int?, int?[])").WithLocation(20, 40)); + } + + [Fact] + public void AddMethod_07() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + public class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(string s) { } + internal void __AddInternal(T t) { _list.Add(t); } + } + namespace N + { + internal static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } + } + """; + var comp = CreateCompilation(sourceA); + var refA = comp.EmitToImageReference(); + + string sourceB = """ + using N; + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceB, sourceA, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + + comp = CreateCompilation([sourceB, s_collectionExtensions], references: [refA]); + comp.VerifyEmitDiagnostics( + // (8,32): error CS1950: The best overloaded Add method 'MyCollection.Add(string)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "x").WithArguments("MyCollection.Add(string)").WithLocation(8, 32), + // (8,32): error CS1503: Argument 1: cannot convert from 'int' to 'string' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "int", "string").WithLocation(8, 32), + // (8,35): error CS1503: Argument 1: cannot convert from 'int' to 'string' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "..y").WithArguments("1", "int", "string").WithLocation(8, 35), + // (8,37): error CS1950: The best overloaded Add method 'MyCollection.Add(string)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "y").WithArguments("MyCollection.Add(string)").WithLocation(8, 37)); + } + + [Fact] + public void AddMethod_08() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(string s) => throw null; + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, int i) { collection.__AddInternal(i); } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceB1, sourceA, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + + string sourceB2 = """ + class Program + { + static void Main() + { + object x = 1; + object[] y = [2, 3]; + MyCollection z = [x, ..y]; + } + } + """; + var comp = CreateCompilation([sourceB2, sourceA]); + comp.VerifyEmitDiagnostics( + // (7,35): error CS1950: The best overloaded Add method 'MyCollection.Add(string)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "x").WithArguments("MyCollection.Add(string)").WithLocation(7, 35), + // (7,35): error CS1503: Argument 1: cannot convert from 'object' to 'string' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "object", "string").WithLocation(7, 35), + // (7,38): error CS1503: Argument 1: cannot convert from 'object' to 'string' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "..y").WithArguments("1", "object", "string").WithLocation(7, 38), + // (7,40): error CS1950: The best overloaded Add method 'MyCollection.Add(string)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "y").WithArguments("MyCollection.Add(string)").WithLocation(7, 40)); + } + + [Fact] + public void AddMethod_09() + { + // public struct MyCollection : IEnumerable + // { + // IEnumerator IEnumerable.GetEnumerator() => null; + // public void Add(T x, params T y) => throw null; + // } + string sourceA = $$""" + .class public sealed MyCollection`1 + extends [mscorlib]System.ValueType + implements [mscorlib]System.Collections.IEnumerable + { + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } + .method private instance class [mscorlib]System.Collections.IEnumerator GetEnumerator() + { + .override [mscorlib]System.Collections.IEnumerable::GetEnumerator + ldnull + ret + } + .method public instance void Add(!T x, !T y) + { + .param [2] + .custom instance void [mscorlib]System.ParamArrayAttribute::.ctor() = ( 01 00 00 00 ) + ldnull + ret + } + } + """; + var refA = CompileIL(sourceA); + + string sourceB = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + MyCollection w = new() { x }; + } + } + """; + var comp = CreateCompilation(sourceB, references: [refA]); + comp.VerifyEmitDiagnostics( + // (7,31): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y]").WithArguments("MyCollection").WithLocation(7, 31), + // (8,39): error CS7036: There is no argument given that corresponds to the required parameter 'y' of 'MyCollection.Add(int, params int)' + // MyCollection w = new() { x }; + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "x").WithArguments("y", "MyCollection.Add(int, params int)").WithLocation(8, 39)); + } + + [Fact] + public void AddMethod_ParamCollectionAttribute_01() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(T x, params List y) + { + _list.Add(x); + foreach (var i in y) + _list.Add(i); + } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceB1, sourceA, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + } + + [Fact] + public void AddMethod_ParamCollectionAttribute_02() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(U x, params MyCollection y) where U : T + { + _list.Add(x); + foreach (var i in y) + _list.Add(i); + } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceB1, sourceA, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + } + + [Fact] + public void AddMethod_ParamCollectionAttribute_03() + { + // public struct MyCollection : IEnumerable + // { + // IEnumerator IEnumerable.GetEnumerator() => null; + // public void Add(object x, [ParamCollection] object y) => throw null; + // } + string sourceA = $$""" + .class public System.Runtime.CompilerServices.ParamCollectionAttribute extends [mscorlib]System.Attribute + { + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } + } + .class public sealed MyCollection`1 + extends [mscorlib]System.ValueType + implements [mscorlib]System.Collections.IEnumerable + { + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } + .method private instance class [mscorlib]System.Collections.IEnumerator GetEnumerator() + { + .override [mscorlib]System.Collections.IEnumerable::GetEnumerator + ldnull + ret + } + .method public instance void Add(object x, object y) + { + .param [2] + .custom instance void System.Runtime.CompilerServices.ParamCollectionAttribute::.ctor() = ( 01 00 00 00 ) + ldnull + ret + } + } + """; + var refA = CompileIL(sourceA); + + string sourceB = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + } + } + """; + var comp = CreateCompilation(sourceB, references: [refA]); + comp.VerifyEmitDiagnostics( + // (7,31): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y]").WithArguments("MyCollection").WithLocation(7, 31)); + } + + [Fact] + public void AddMethod_Extension_01() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection) { collection.__AddInternal(default); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (19,32): error CS1501: No overload for method 'Add' takes 1 arguments + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_BadArgCount, "[x, ..y, null]").WithArguments("Add", "1").WithLocation(19, 32)); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref readonly")] + public void AddMethod_Extension_02(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, T t, {{refKind}} int x = 1, {{refKind}} int y = 2) { collection.__AddInternal(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref readonly")] + public void AddMethod_Extension_03(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, T t, {{refKind}} int x, {{refKind}} int y = 2) { collection.__AddInternal(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + } + } + """; + var comp = CreateCompilation(source); + if (refKind == "ref readonly") + { + comp.VerifyEmitDiagnostics( + // (11,110): warning CS9200: A default value is specified for 'ref readonly' parameter 'y', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void Add(this MyCollection collection, T t, ref readonly int x, ref readonly int y = 2) { collection.__AddInternal(t); } + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "2").WithArguments("y").WithLocation(11, 110), + // (19,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y, null]").WithArguments("MyCollection").WithLocation(19, 32)); + } + else if (refKind == "in") + { + comp.VerifyEmitDiagnostics( + // (19,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y, null]").WithArguments("MyCollection").WithLocation(19, 32)); + } + else + { + comp.VerifyEmitDiagnostics( + // (19,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y, null]").WithArguments("MyCollection").WithLocation(19, 32)); + } + } + + [Fact] + public void AddMethod_Extension_04() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, params T[] args) + { + if (args is null) return; + foreach (var a in args) + collection.__AddInternal(a); + } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB1, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + + string sourceB2 = """ + class Program + { + static void Main() + { + MyCollection x = new() { (int?)null, null }; + MyCollection y = [(int?)null, null]; + x.Report(); + y.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB2, s_collectionExtensions], expectedOutput: "[null], [null], "); + } + + [Fact] + public void AddMethod_Extension_05() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, T x, params T[] y) + { + collection.__AddInternal(x); + foreach (var a in y) + collection.__AddInternal(a); + } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3, null], "); + } + + [Fact] + public void AddMethod_Extension_06() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, T x, T y = default, params T[] z) + { + collection.__AddInternal(x); + collection.__AddInternal(y); + foreach (var a in z) + collection.__AddInternal(a); + } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, null, 2, null, 3, null], "); + } + + [Fact] + public void AddMethod_Extension_07() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + public class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + public static class Extensions + { + public static void Add(this MyCollection collection, string s) { } + } + namespace N + { + internal static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } + } + """; + var comp = CreateCompilation(sourceA); + var refA = comp.EmitToImageReference(); + + string sourceB = """ + using N; + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceB, sourceA, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + + comp = CreateCompilation([sourceB, s_collectionExtensions], references: [refA]); + comp.VerifyEmitDiagnostics( + // (8,32): error CS1950: The best overloaded Add method 'Extensions.Add(MyCollection, string)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "x").WithArguments("Extensions.Add(MyCollection, string)").WithLocation(8, 32), + // (8,32): error CS1503: Argument 2: cannot convert from 'int' to 'string' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("2", "int", "string").WithLocation(8, 32), + // (8,35): error CS1503: Argument 2: cannot convert from 'int' to 'string' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "..y").WithArguments("2", "int", "string").WithLocation(8, 35), + // (8,37): error CS1950: The best overloaded Add method 'Extensions.Add(MyCollection, string)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "y").WithArguments("Extensions.Add(MyCollection, string)").WithLocation(8, 37)); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref")] + [InlineData("ref readonly")] + public void AddMethod_Extension_08A(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + struct MyCollection : IEnumerable + { + private List _list; + IEnumerator IEnumerable.GetEnumerator() => GetList().GetEnumerator(); + internal void __AddInternal(T t) { GetList().Add(t); } + private List GetList() => _list ??= new(); + } + static class Extensions + { + public static void Add(this {{refKind}} MyCollection collection, T t) { collection.__AddInternal(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + string expectedOutput = (refKind == "ref") ? "[1, 2, 3], " : "[], "; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: expectedOutput); + } + + [Theory] + [InlineData("")] + [InlineData("in")] + [InlineData("ref")] + [InlineData("ref readonly")] + public void AddMethod_Extension_08B(string refKind) + { + string source = $$""" + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list; + IEnumerator IEnumerable.GetEnumerator() => GetList().GetEnumerator(); + internal void __AddInternal(T t) { GetList().Add(t); } + private List GetList() => _list ??= new(); + } + static class Extensions + { + public static void Add(this {{refKind}} MyCollection collection, T t) { collection.__AddInternal(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); + switch (refKind) + { + case "": + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + break; + case "in": + case "ref readonly": + comp.VerifyEmitDiagnostics( + // (12,24): error CS8338: The first 'in' or 'ref readonly' parameter of the extension method 'Add' must be a concrete (non-generic) value type. + // public static void Add(this in MyCollection collection, T t) { collection.__AddInternal(t); } + Diagnostic(ErrorCode.ERR_InExtensionMustBeValueType, "Add").WithArguments("Add").WithLocation(12, 24), + // (20,31): error CS1061: 'MyCollection' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'MyCollection' could be found (are you missing a using directive or an assembly reference?) + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[x, ..y]").WithArguments("MyCollection", "Add").WithLocation(20, 31)); + break; + case "ref": + comp.VerifyEmitDiagnostics( + // (12,24): error CS8337: The first parameter of a 'ref' extension method 'Add' must be a value type or a generic type constrained to struct. + // public static void Add(this ref MyCollection collection, T t) { collection.__AddInternal(t); } + Diagnostic(ErrorCode.ERR_RefExtensionMustBeValueTypeOrConstrainedToOne, "Add").WithArguments("Add").WithLocation(12, 24), + // (20,31): error CS1061: 'MyCollection' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'MyCollection' could be found (are you missing a using directive or an assembly reference?) + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[x, ..y]").WithArguments("MyCollection", "Add").WithLocation(20, 31)); + break; + } + } + + [Fact] + public void AddMethod_Extension_09() + { + // public struct MyCollection : IEnumerable + // { + // IEnumerator IEnumerable.GetEnumerator() => null; + // } + // public static class Extensions + // { + // public static void Add(this out MyCollection collection, T t) => throw null; + // } + string sourceA = """ + .assembly extern mscorlib { .ver 4:0:0:0 .publickeytoken = (B7 7A 5C 56 19 34 E0 89) } + .assembly '<>' + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + } + .class public sealed MyCollection`1 + extends [mscorlib]System.ValueType + implements [mscorlib]System.Collections.IEnumerable + { + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ret } + .method private instance class [mscorlib]System.Collections.IEnumerator GetEnumerator() + { + .override [mscorlib]System.Collections.IEnumerable::GetEnumerator + ldnull + ret + } + } + .class public abstract sealed Extensions + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .method public hidebysig static void Add([out] valuetype MyCollection`1& collection, !!T t) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + ldnull + throw + } + } + """; + var refA = CompileIL(sourceA, prependDefaultHeader: false); + + string sourceB = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + } + } + """; + var comp = CreateCompilation(sourceB, references: [refA]); + comp.VerifyEmitDiagnostics( + // (7,31): error CS1061: 'MyCollection' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'MyCollection' could be found (are you missing a using directive or an assembly reference?) + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[x, ..y]").WithArguments("MyCollection", "Add").WithLocation(7, 31)); + } + + [Fact] + public void AddMethod_Extension_10_WrongThisType() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this string collection, int x) => throw null; + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + MyCollection w = new() { x }; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (19,32): error CS1929: 'MyCollection' does not contain a definition for 'Add' and the best extension method overload 'Extensions.Add(string, int)' requires a receiver of type 'string' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadInstanceArgType, "[x, ..y]").WithArguments("MyCollection", "Add", "Extensions.Add(string, int)", "string").WithLocation(19, 32), + // (19,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y]").WithArguments("MyCollection").WithLocation(19, 32), + // (20,32): error CS1929: 'MyCollection' does not contain a definition for 'Add' and the best extension method overload 'Extensions.Add(string, int)' requires a receiver of type 'string' + // MyCollection w = new() { x }; + Diagnostic(ErrorCode.ERR_BadInstanceArgType, "new() { x }").WithArguments("MyCollection", "Add", "Extensions.Add(string, int)", "string").WithLocation(20, 32), + // (20,40): error CS1950: The best overloaded Add method 'Extensions.Add(string, int)' for the collection initializer has some invalid arguments + // MyCollection w = new() { x }; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "x").WithArguments("Extensions.Add(string, int)").WithLocation(20, 40) + ); + } + + [Fact] + public void AddMethod_Extension_11_WrongThisType() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this IEnumerable collection, int x) => throw null; + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + MyCollection w = new() { x }; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (19,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y]").WithArguments("MyCollection").WithLocation(19, 32), + // (20,40): error CS1061: 'MyCollection' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'MyCollection' could be found (are you missing a using directive or an assembly reference?) + // MyCollection w = new() { x }; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "x").WithArguments("MyCollection", "Add").WithLocation(20, 40) + ); + } + + [Fact] + public void AddMethod_Extension_12_WrongThisType() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB1, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + + string sourceB2 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + } + } + """; + var comp = CreateCompilation([sourceA, sourceB2]); + comp.VerifyEmitDiagnostics( + // (7,39): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y]").WithArguments("MyCollection").WithLocation(7, 39)); + } + + [Fact] + public void AddMethod_Extension_13_WrongThisType() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this IEnumerable collection, T t) { ((MyCollection)collection).__AddInternal(t); } + } + """; + string sourceB = """ + class Program + { + static void Main() + { + string x = "1"; + string[] y = ["2", "3"]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + } + + [Fact] + public void AddMethod_Extension_14_ConstraintsViolated() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, params T[] args) + where T : struct + { + if (args is null) return; + foreach (var a in args) + collection.__AddInternal(a); + } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + MyCollection w = new() { x }; + } + } + """; + CreateCompilation([sourceA, sourceB1, s_collectionExtensions]).VerifyDiagnostics( + // (7,32): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y]").WithArguments("MyCollection").WithLocation(7, 32), + // (9,40): error CS0453: The type 'int?' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Extensions.Add(MyCollection, params T[])' + // MyCollection w = new() { x }; + Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "x").WithArguments("Extensions.Add(MyCollection, params T[])", "T", "int?").WithLocation(9, 40) + ); + } + + [Fact] + public void AddMethod_Extension_15_Dynamic() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this dynamic d, T t) { d.__AddInternal(t); } + } + """; + string sourceB = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + } + } + """; + var comp = CreateCompilation([sourceA, sourceB]); + comp.VerifyEmitDiagnostics( + // (11,36): error CS1103: The first parameter of an extension method cannot be of type 'dynamic' + // public static void Add(this dynamic d, T t) { d.__AddInternal(t); } + Diagnostic(ErrorCode.ERR_BadTypeforThis, "dynamic").WithArguments("dynamic").WithLocation(11, 36)); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72769")] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/72769")] + public void AddMethod_RefOmittedArguments() + { + string sourceA = """ + using System; + using System.Collections; + using System.Runtime.InteropServices; + + [ComImport] + [Guid("5CDF1E39-B461-4A9B-9359-1D6F7DECE1B3")] + class MyCollection : IEnumerable + { + extern IEnumerator IEnumerable.GetEnumerator(); + } + + static class Extensions + { + public static void Add(this MyCollection collection, ref T x) => throw null; + } + """; + string sourceB = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + MyCollection w = new() { x }; + } + } + """; + var comp = CreateCompilation([sourceA, sourceB]); + // https://github.com/dotnet/roslyn/issues/72769: VerifyEmitDiagnostics() results in Debug.Assert + // failures in LocalRewriter.MakeCollectionInitializer() and GetEffectiveArgumentRefKinds(). + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void AddMethod_Base() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + abstract class MyCollectionBase + { + protected abstract void __AddInternal(T t); + public void Add(T t) => __AddInternal(t); + } + class MyCollection : MyCollectionBase, IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + protected override void __AddInternal(T t) { _list.Add(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + } + + [Fact] + public void AddMethod_Derived() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + protected void __AddInternal(T t) { _list.Add(t); } + } + class MyCollectionDerived : MyCollection + { + public void Add(T t) => __AddInternal(t); + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollectionDerived z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + } + + [Theory] + [InlineData("class")] + [InlineData("struct")] + public void AddMethod_ExplicitImplementation(string structOrClass) + { + string sourceA = $$""" + using System.Collections; + using System.Collections.Generic; + interface IAdd + { + void Add(T t); + } + {{structOrClass}} MyCollection : IAdd, IEnumerable + { + private List _list; + IEnumerator IEnumerable.GetEnumerator() => GetList().GetEnumerator(); + void IAdd.Add(T t) { GetList().Add(t); } + private List GetList() => _list ??= new(); + } + """; + string sourceB = """ + class Program + { + static void Main() + { + object x = 1; + object[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + string sourceC = $$""" + static class Extensions + { + public static void Add(this {{(structOrClass == "struct" ? "ref" : "")}} T collection, U u) + where T : {{structOrClass}}, IAdd { - List items = [new()]; + collection.Add(u); } } """; - var comp = CreateCompilation(source); - comp.VerifyDiagnostics(); - - var tree = comp.SyntaxTrees[0]; - var model = comp.GetSemanticModel(tree); - var node = tree.GetRoot().DescendantNodes().OfType().Single(); - var info = model.GetSymbolInfo(node); - Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); + var comp = CreateCompilation([sourceA, sourceB, s_collectionExtensions]); + comp.VerifyEmitDiagnostics( + // (7,34): error CS1061: 'MyCollection' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'MyCollection' could be found (are you missing a using directive or an assembly reference?) + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[x, ..y]").WithArguments("MyCollection", "Add").WithLocation(7, 34)); - model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ - ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Generic.List..ctor()) (OperationKind.CollectionExpression, Type: System.Collections.Generic.List) (Syntax: '[new()]') - Elements(1): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') - Arguments(0) - Initializer: - null - """); + CompileAndVerify([sourceA, sourceB, sourceC, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); } [Fact] - public void TargetTypedElement_PublicAPI_Array() + public void AddMethod_Static() { - var source = """ - class C + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public static void Add(T t) => throw null; + internal void __AddInternal(T t) { _list.Add(t); } + } + """; + string sourceB = """ + class Program { static void Main() { - object[] items = [new()]; + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); } } """; + string sourceC = """ + static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } + """; - var comp = CreateCompilation(source); - comp.VerifyDiagnostics(); - - var tree = comp.SyntaxTrees[0]; - var model = comp.GetSemanticModel(tree); - var node = tree.GetRoot().DescendantNodes().OfType().Single(); - var info = model.GetSymbolInfo(node); - Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); + var comp = CreateCompilation([sourceA, sourceB, s_collectionExtensions]); + comp.VerifyEmitDiagnostics( + // (7,34): error CS1921: The best overloaded method match for 'MyCollection.Add(object)' has wrong signature for the initializer element. The initializable Add must be an accessible instance method. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_InitializerAddHasWrongSignature, "[x, ..y]").WithArguments("MyCollection.Add(object)").WithLocation(7, 34)); - model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ - ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Object[]) (Syntax: '[new()]') - Elements(1): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') - Arguments(0) - Initializer: - null - """); + CompileAndVerify([sourceA, sourceB, sourceC, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); } [Fact] - public void TargetTypedElement_PublicAPI_Span() + public void AddMethod_Generic_01() { - var source = """ - using System; + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(U u) { _list.Add(u is T t ? t : default); } + } + """; - class C + string sourceB1 = """ + class Program { static void Main() { - Span items = [new()]; + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); } } """; + CompileAndVerify([sourceA, sourceB1, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); - comp.VerifyDiagnostics(); - - var tree = comp.SyntaxTrees[0]; - var model = comp.GetSemanticModel(tree); - var node = tree.GetRoot().DescendantNodes().OfType().Single(); - var info = model.GetSymbolInfo(node); - Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); - - model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ - ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Span) (Syntax: '[new()]') - Elements(1): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') - Arguments(0) - Initializer: - null - """); + string sourceB2 = """ + class Program + { + static void Main() + { + MyCollection z = [null]; + } + } + """; + var comp = CreateCompilation([sourceA, sourceB2]); + comp.VerifyEmitDiagnostics( + // (5,35): error CS0411: The type arguments for method 'MyCollection.Add(U)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // MyCollection z = [null]; + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "null").WithArguments("MyCollection.Add(U)").WithLocation(5, 35)); } [Fact] - public void TargetTypedElement_PublicAPI_ReadOnlySpan() + public void AddMethod_Generic_02() { - var source = """ - using System; - - class C + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(T t) { _list.Add(t); } + } + class Program { static void Main() { - ReadOnlySpan items = [new()]; + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; } } """; - - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); - comp.VerifyDiagnostics(); - - var tree = comp.SyntaxTrees[0]; - var model = comp.GetSemanticModel(tree); - var node = tree.GetRoot().DescendantNodes().OfType().Single(); - var info = model.GetSymbolInfo(node); - Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); - - model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ - ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.ReadOnlySpan) (Syntax: '[new()]') - Elements(1): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') - Arguments(0) - Initializer: - null - """); + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (15,34): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y, null]").WithArguments("MyCollection").WithLocation(15, 34)); } [Fact] - public void TargetTypedElement_PublicAPI_ImmutableArray() + public void AddMethod_Generic_03() { - var source = """ - using System.Collections.Immutable; - - class C + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(T t, U u = default) { _list.Add(t); } + } + class Program { static void Main() { - ImmutableArray items = [new()]; + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; } } """; - - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); - comp.VerifyDiagnostics(); - - var tree = comp.SyntaxTrees[0]; - var model = comp.GetSemanticModel(tree); - var node = tree.GetRoot().DescendantNodes().OfType().Single(); - var info = model.GetSymbolInfo(node); - Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); - - model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ - ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Immutable.ImmutableArray System.Collections.Immutable.ImmutableArray.Create(System.ReadOnlySpan items)) (OperationKind.CollectionExpression, Type: System.Collections.Immutable.ImmutableArray) (Syntax: '[new()]') - Elements(1): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') - Arguments(0) - Initializer: - null - """); + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (15,34): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y, null]").WithArguments("MyCollection").WithLocation(15, 34)); } [Fact] - public void TargetTypedElement_PublicAPI_IEnumerableT() + public void AddMethod_Generic_04() { - var source = """ + string sourceA = """ + using System.Collections; using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, U u) { collection.__AddInternal(u is T t ? t : default); } + } + """; - class C + string sourceB1 = """ + class Program { static void Main() { - IEnumerable items = [new()]; + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); } } """; + CompileAndVerify([sourceA, sourceB1, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); - var comp = CreateCompilation(source); - comp.VerifyDiagnostics(); - - var tree = comp.SyntaxTrees[0]; - var model = comp.GetSemanticModel(tree); - var node = tree.GetRoot().DescendantNodes().OfType().Single(); - var info = model.GetSymbolInfo(node); - Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); + string sourceB2 = """ + class Program + { + static void Main() + { + MyCollection z = [null]; + } + } + """; + var comp = CreateCompilation([sourceA, sourceB2]); + comp.VerifyEmitDiagnostics( + // (5,35): error CS0411: The type arguments for method 'Extensions.Add(MyCollection, U)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // MyCollection z = [null]; + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "null").WithArguments("Extensions.Add(MyCollection, U)").WithLocation(5, 35)); + } - model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ - ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Collections.Generic.IEnumerable) (Syntax: '[new()]') - Elements(1): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') - Arguments(0) - Initializer: - null - """); + [Fact] + public void AddMethod_Generic_05() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (19,34): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[x, ..y, null]").WithArguments("MyCollection").WithLocation(19, 34)); } [Fact] - public void TargetTypedElement_PublicAPI_ImplementsIEnumerable() + public void AddMethod_Generic_06() { - var source = """ + string sourceA = """ using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } + """; - class C + string sourceB1 = """ + class Program { static void Main() { - MyCollection items = [new()]; + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); } } + """; + CompileAndVerify([sourceA, sourceB1, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); - class MyCollection : IEnumerable + string sourceB2 = """ + class Program { - IEnumerator IEnumerable.GetEnumerator() => throw null!; - public void Add(object obj) { } + static void Main() + { + MyCollection z = [null]; + } } """; - - var comp = CreateCompilation(source); - comp.VerifyDiagnostics(); - - var tree = comp.SyntaxTrees[0]; - var model = comp.GetSemanticModel(tree); - var node = tree.GetRoot().DescendantNodes().OfType().Single(); - var info = model.GetSymbolInfo(node); - Assert.Equal("object.Object()", info.Symbol.ToDisplayString()); - - model.VerifyOperationTree(tree.GetRoot().DescendantNodes().OfType().Single(), """ - ICollectionExpressionOperation (1 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[new()]') - Elements(1): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'new()') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - IObjectCreationOperation (Constructor: System.Object..ctor()) (OperationKind.ObjectCreation, Type: System.Object) (Syntax: 'new()') - Arguments(0) - Initializer: - null - """); + var comp = CreateCompilation([sourceA, sourceB2]); + comp.VerifyEmitDiagnostics( + // (5,35): error CS0411: The type arguments for method 'Extensions.Add(MyCollection, T)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // MyCollection z = [null]; + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "null").WithArguments("Extensions.Add(MyCollection, T)").WithLocation(5, 35)); } - [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] [Fact] - public void Add_ParamsArray_01() + public void AddMethod_Generic_07() { string source = """ - using System; + using System.Collections; using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } static class Extensions { - public static void Add(this ICollection collection, params T[] elements) - { - foreach (T element in elements) - collection.Add(element); - } + public static void Add(this MyCollection collection, T x, U y = default, T z = default) { collection.__AddInternal(x is U u ? u : default); } } class Program { - static Dictionary CreateDictionary(ICollection> collection) - { - return /**/[..collection]/**/; - } static void Main() { - var v = new KeyValuePair[] { new("a", "b"), new("c", "d") }; - var d = CreateDictionary(v); - foreach (var kvp in d) - Console.Write("({0}, {1}), ", kvp.Key, kvp.Value); + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); } } """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + } - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - var verifier = CompileAndVerify(comp, expectedOutput: "(a, b), (c, d), "); + [Fact] + public void AddMethod_Generic_08() + { + string sourceA = """ + using System; + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(Func f) { _list.Add(f); } + } + """; - verifier.VerifyIL("Extensions.Add(this System.Collections.Generic.ICollection, params T[])", """ + string sourceB1 = """ + class Program { - // Code size 32 (0x20) - .maxstack 2 - .locals init (T[] V_0, - int V_1, - T V_2) //element - IL_0000: ldarg.1 - IL_0001: stloc.0 - IL_0002: ldc.i4.0 - IL_0003: stloc.1 - IL_0004: br.s IL_0019 - IL_0006: ldloc.0 - IL_0007: ldloc.1 - IL_0008: ldelem "T" - IL_000d: stloc.2 - IL_000e: ldarg.0 - IL_000f: ldloc.2 - IL_0010: callvirt "void System.Collections.Generic.ICollection.Add(T)" - IL_0015: ldloc.1 - IL_0016: ldc.i4.1 - IL_0017: add - IL_0018: stloc.1 - IL_0019: ldloc.1 - IL_001a: ldloc.0 - IL_001b: ldlen - IL_001c: conv.i4 - IL_001d: blt.s IL_0006 - IL_001f: ret + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; + } } - """); + """; + var comp = CreateCompilation([sourceA, sourceB1]); + comp.VerifyEmitDiagnostics( + // (7,35): error CS0411: The type arguments for method 'MyCollection.Add(Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "x").WithArguments("MyCollection.Add(System.Func)").WithLocation(7, 35), + // (7,40): error CS0411: The type arguments for method 'MyCollection.Add(Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "y").WithArguments("MyCollection.Add(System.Func)").WithLocation(7, 40), + // (7,43): error CS0411: The type arguments for method 'MyCollection.Add(Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "null").WithArguments("MyCollection.Add(System.Func)").WithLocation(7, 43)); - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Generic.Dictionary..ctor()) (OperationKind.CollectionExpression, Type: System.Collections.Generic.Dictionary) (Syntax: '[..collection]') - Elements(1): - ISpreadOperation (ElementType: System.Collections.Generic.KeyValuePair) (OperationKind.Spread, Type: null) (Syntax: '..collection') - Operand: - IParameterReferenceOperation: collection (OperationKind.ParameterReference, Type: System.Collections.Generic.ICollection>) (Syntax: 'collection') - ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (Identity) - """); + string sourceB2 = """ + using System; + class Program + { + static void Main() + { + Func x = _ => 1; + Func[] y = [_ => "2"]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB2, s_collectionExtensions], expectedOutput: "[System.Func`2[System.Object,System.Int32], System.Func`2[System.Object,System.String]], "); } - [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] [Fact] - public void Add_ParamsCollection_01() + public void AddMethod_Generic_09() { - string source = """ + string sourceA = """ using System; + using System.Collections; using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(Delegate d) { _list.Add(d); } + } static class Extensions { - public static void Add(this ICollection collection, params IEnumerable elements) - { - foreach (T element in elements) - collection.Add(element); - } + public static void Add(this MyCollection collection, Func f) { collection.__AddInternal(f); } } + """; + + string sourceB1 = """ class Program { - static Dictionary CreateDictionary(ICollection> collection) + static void Main() { - return /**/[..collection]/**/; + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y, null]; } + } + """; + var comp = CreateCompilation([sourceA, sourceB1]); + comp.VerifyEmitDiagnostics( + // (7,35): error CS0411: The type arguments for method 'Extensions.Add(MyCollection, Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "x").WithArguments("Extensions.Add(MyCollection, System.Func)").WithLocation(7, 35), + // (7,40): error CS0411: The type arguments for method 'Extensions.Add(MyCollection, Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "y").WithArguments("Extensions.Add(MyCollection, System.Func)").WithLocation(7, 40), + // (7,43): error CS0411: The type arguments for method 'Extensions.Add(MyCollection, Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // MyCollection z = [x, ..y, null]; + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "null").WithArguments("Extensions.Add(MyCollection, System.Func)").WithLocation(7, 43)); + + string sourceB2 = """ + using System; + class Program + { static void Main() { - var v = new KeyValuePair[] { new("a", "b"), new("c", "d") }; - var d = CreateDictionary(v); - foreach (var kvp in d) - Console.Write("({0}, {1}), ", kvp.Key, kvp.Value); + Func x = _ => 1; + Func[] y = [_ => "2"]; + MyCollection z = [x, ..y]; + z.Report(); } } """; - - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); - CompileAndVerify(comp, expectedOutput: "(a, b), (c, d), ").VerifyDiagnostics(); - - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Generic.Dictionary..ctor()) (OperationKind.CollectionExpression, Type: System.Collections.Generic.Dictionary) (Syntax: '[..collection]') - Elements(1): - ISpreadOperation (ElementType: System.Collections.Generic.KeyValuePair) (OperationKind.Spread, Type: null) (Syntax: '..collection') - Operand: - IParameterReferenceOperation: collection (OperationKind.ParameterReference, Type: System.Collections.Generic.ICollection>) (Syntax: 'collection') - ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (Identity) - """); + CompileAndVerify([sourceA, sourceB2, s_collectionExtensions], expectedOutput: "[System.Func`2[System.Object,System.Int32], System.Func`2[System.Object,System.String]], "); } - [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + // [Obsolete] attribute is ignored when checking for Add for conversion. [Fact] - public void Add_ParamsArray_02() + public void AddMethod_Obsolete_01() { string source = """ + using System; using System.Collections; using System.Collections.Generic; - class MyCollection : IEnumerable + class MyCollection : IEnumerable { - private List _list = new(); - public void Add(params T[] x) => _list.AddRange(x); - public IEnumerator GetEnumerator() => _list.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + [Obsolete("do not use", error: true)] + public void Add(string s) => throw null; + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } } class Program { static void Main() { int x = 1; - MyCollection y = [2, 3]; - MyCollection z = /**/[x, ..y]/**/; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; z.Report(); } } """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); + } - var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); - - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') - Elements(2): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') - ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') - Operand: - ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') - ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (Boxing) - """); + // [Obsolete] attribute is ignored when checking for Add for conversion. + [Fact] + public void AddMethod_Obsolete_02() + { + string source = """ + using N; + using System; + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions + { + [Obsolete("do not use", error: true)] + public static void Add(this MyCollection collection, string s) => throw null; + } + namespace N + { + static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } + } + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); + } + } + """; + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); } - [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] [Fact] - public void Add_ParamsArray_03() + public void AddMethod_Unsafe_01() { - string source = """ + string sourceA = """ using System.Collections; using System.Collections.Generic; - class MyCollection : IEnumerable + class MyCollection : IEnumerable { - private List _list = new(); + private readonly List _list = new(); public IEnumerator GetEnumerator() => _list.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - internal void __AddRange(T[] x) { _list.AddRange(x); } - } - static class Extensions - { - public static void Add(this MyCollection c, params T[] x) { c.__AddRange(x); } + unsafe public void Add(void* p) => throw null; + internal void __AddInternal(T t) { _list.Add(t); } } + """; + string sourceB = """ class Program { static void Main() { int x = 1; - MyCollection y = [2, 3]; - MyCollection z = /**/[x, ..y]/**/; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; z.Report(); } } """; + string sourceC = """ + static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } + """; - var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + var comp = CreateCompilation([sourceA, sourceB, s_collectionExtensions], options: TestOptions.UnsafeReleaseExe); + comp.VerifyEmitDiagnostics( + // (7,35): error CS1950: The best overloaded Add method 'MyCollection.Add(void*)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "x").WithArguments("MyCollection.Add(void*)").WithLocation(7, 35), + // (7,35): error CS1503: Argument 1: cannot convert from 'int' to 'void*' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "int", "void*").WithLocation(7, 35), + // (7,38): error CS1503: Argument 1: cannot convert from 'int' to 'void*' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "..y").WithArguments("1", "int", "void*").WithLocation(7, 38), + // (7,40): error CS1950: The best overloaded Add method 'MyCollection.Add(void*)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "y").WithArguments("MyCollection.Add(void*)").WithLocation(7, 40)); - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') - Elements(2): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') - ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') - Operand: - ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') - ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (Boxing) - """); + CompileAndVerify([sourceA, sourceB, sourceC, s_collectionExtensions], options: TestOptions.UnsafeReleaseExe, expectedOutput: "[1, 2, 3], "); } [Fact] - public void Add_ParamsArray_04() + public void AddMethod_Unsafe_02() { - string source = """ + string sourceA = """ using System.Collections; using System.Collections.Generic; - class MyCollection : IEnumerable + class MyCollection : IEnumerable { - private List _list = new(); - public void Add(T x, params T[] y) => _list.Add(x); + private readonly List _list = new(); public IEnumerator GetEnumerator() => _list.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + internal void __AddInternal(T t) { _list.Add(t); } + } + static class Extensions1 + { + unsafe public static void Add(this MyCollection collection, void* p) => throw null; + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + } } + """; + var comp = CreateCompilation([sourceA, sourceB1, s_collectionExtensions], options: TestOptions.UnsafeReleaseExe); + comp.VerifyEmitDiagnostics( + // (7,35): error CS1950: The best overloaded Add method 'Extensions1.Add(MyCollection, void*)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "x").WithArguments("Extensions1.Add(MyCollection, void*)").WithLocation(7, 35), + // (7,35): error CS1503: Argument 2: cannot convert from 'int' to 'void*' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("2", "int", "void*").WithLocation(7, 35), + // (7,38): error CS1503: Argument 2: cannot convert from 'int' to 'void*' + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgType, "..y").WithArguments("2", "int", "void*").WithLocation(7, 38), + // (7,40): error CS1950: The best overloaded Add method 'Extensions1.Add(MyCollection, void*)' for the collection initializer has some invalid arguments + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_BadArgTypesForCollectionAdd, "y").WithArguments("Extensions1.Add(MyCollection, void*)").WithLocation(7, 40)); + + string sourceB2 = """ + using N; class Program { static void Main() { int x = 1; - MyCollection y = [2, 3]; - MyCollection z = /**/[x, ..y]/**/; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; z.Report(); } } + namespace N + { + static class Extensions2 + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } + } """; - - var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); - - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') - Elements(2): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') - ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') - Operand: - ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') - ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (Boxing) - """); + CompileAndVerify([sourceA, sourceB2, s_collectionExtensions], options: TestOptions.UnsafeReleaseExe, expectedOutput: "[1, 2, 3], "); } [Fact] - public void Add_ParamsArray_05() + public void AddMethod_RefStruct_01() { string source = """ + using System; using System.Collections; using System.Collections.Generic; - class MyCollection : IEnumerable + ref struct R { } + class MyCollection : IEnumerable { - private List _list = new(); - public IEnumerator GetEnumerator() => _list.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - internal void __Add(T x) { _list.Add(x); } + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(R r) => throw null; + internal void __AddInternal(T t) { _list.Add(t); } } static class Extensions { - public static void Add(this MyCollection c, T x, params T[] y) { c.__Add(x); } + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } } class Program { static void Main() { int x = 1; - MyCollection y = [2, 3]; - MyCollection z = /**/[x, ..y]/**/; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; z.Report(); } } """; - - var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); - - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') - Elements(2): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') - ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') - Operand: - ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') - ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (Boxing) - """); + CompileAndVerify([source, s_collectionExtensions], expectedOutput: "[1, 2, 3], "); } - [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] [Fact] - public void Add_ParamsArray_06() + public void AddMethod_RefStruct_02() { - string sourceA = """ + string source = """ using System; using System.Collections; using System.Collections.Generic; + ref struct R + { + public R(object value) { Value = value; } + public readonly object Value; + } class MyCollection : IEnumerable { - private List _list = new(); - public void Add(params MyCollection[] x) + private List _list = new(); + public MyEnumerator GetEnumerator() => new MyEnumerator(_list); + IEnumerator IEnumerable.GetEnumerator() => throw null; + public void Add(object o) => throw null; + internal void __AddInternal(R r) { _list.Add(r.Value); } + } + class MyEnumerator + { + private List _list; + private int _index = -1; + public MyEnumerator(List list) { _list = list; } + public bool MoveNext() { - Console.Write("Add: "); - x.Report(); - Console.WriteLine(); - _list.AddRange(x); + if (_index < _list.Count) _index++; + return _index < _list.Count; } - public IEnumerator GetEnumerator() => _list.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public R Current => new R(_list[_index]); + } + static class Extensions + { + public static void Add(this MyCollection collection, R r) { collection.__AddInternal(r); } } - """; - - string sourceB1 = """ class Program { static void Main() { - MyCollection x = []; - MyCollection[] y = []; - MyCollection z = /**/[x, ..y]/**/; - z.Report(); + MyCollection x = [new R(1)]; + MyCollection y = [..x, new R(2)]; + foreach (var i in y) + Console.Write("{0}, ", i.Value); } } """; + CompileAndVerify(source, verify: Verification.FailsILVerify, expectedOutput: "1, 2, "); + } - var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: """ - Add: [[]], - [[]], - """); + [Fact] + public void AddMethod_UseSiteErrors() + { + string assemblyA = GetUniqueName(); + string sourceA = """ + public class A { } + """; + var comp = CreateCompilation(sourceA, assemblyName: assemblyA); + var refA = comp.EmitToImageReference(); - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') - Elements(2): - ILocalReferenceOperation: x (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'x') - ISpreadOperation (ElementType: MyCollection) (OperationKind.Spread, Type: null) (Syntax: '..y') - Operand: - ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection[]) (Syntax: 'y') - ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (Identity) - """); + string sourceB = """ + using System.Collections; + using System.Collections.Generic; + public class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(A a) => throw null; + public void __AddInternal(T t) { _list.Add(t); } + } + """; + comp = CreateCompilation(sourceB, references: [refA]); + var refB = comp.EmitToImageReference(); - string sourceB2 = """ + string sourceC = """ + static class Extensions + { + public static void Add(this MyCollection collection, T t) { collection.__AddInternal(t); } + } class Program { static void Main() { - MyCollection x = /**/[[]]/**/; - x.Report(); + int x = 1; + int[] y = [2, 3]; + MyCollection z = [x, ..y]; + z.Report(); } } """; - comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: """ - Add: [], - [], - """); + CompileAndVerify([sourceC, s_collectionExtensions], references: [refA, refB], expectedOutput: "[1, 2, 3], "); - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (1 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[[]]') - Elements(1): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection[], IsImplicit) (Syntax: '[]') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - ICollectionExpressionOperation (0 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: MyCollection[]) (Syntax: '[]') - Elements(0) - """); + comp = CreateCompilation([sourceC, s_collectionExtensions], references: [refB]); + comp.VerifyEmitDiagnostics( + // (11,35): error CS0012: The type 'A' is defined in an assembly that is not referenced. You must add a reference to assembly '2537f385-b53e-4fea-834a-b23059cd7f17, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_NoTypeDef, "x").WithArguments("A", $"{assemblyA}, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(11, 35), + // (11,40): error CS0012: The type 'A' is defined in an assembly that is not referenced. You must add a reference to assembly '2537f385-b53e-4fea-834a-b23059cd7f17, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + // MyCollection z = [x, ..y]; + Diagnostic(ErrorCode.ERR_NoTypeDef, "y").WithArguments("A", $"{assemblyA}, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(11, 40)); } - [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] - [Fact] - public void Add_ParamsArray_07() - { - string sourceA = """ + [Fact] + public void AddMethod_UseSiteErrors_ParamCollection() + { + string assemblyA = GetUniqueName(); + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + public class MyCollectionA : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(T t) { _list.Add(t); } + } + """; + var comp = CreateCompilation(sourceA, assemblyName: assemblyA); + var refA = comp.EmitToImageReference(); + + string sourceB = """ using System.Collections; using System.Collections.Generic; - struct MyCollection : IEnumerable + public class MyCollectionB : IEnumerable { - private List _list; - public void Add(params MyCollection?[] x) => GetList().AddRange(x); - public IEnumerator GetEnumerator() => GetList().GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - private List GetList() => _list ??= new(); + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(U x, params MyCollectionA y) where U : T { _list.Add(x); _list.AddRange(y); } } """; + comp = CreateCompilation(sourceB, references: [refA]); + var refB = comp.EmitToImageReference(); - string sourceB1 = """ + string sourceC = """ class Program { static void Main() { - MyCollection x = []; - MyCollection[] y = []; - MyCollection z = /**/[x, ..y]/**/; + int x = 1; + int[] y = [2, 3]; + MyCollectionB z = [x, ..y]; z.Report(); } } """; - var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[[]], "); + CompileAndVerify([sourceC, s_collectionExtensions], references: [refA, refB], expectedOutput: "[1, 2, 3], "); - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') - Elements(2): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection?, IsImplicit) (Syntax: 'x') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILocalReferenceOperation: x (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'x') - ISpreadOperation (ElementType: MyCollection) (OperationKind.Spread, Type: null) (Syntax: '..y') - Operand: - ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection[]) (Syntax: 'y') - ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (ImplicitNullable) - """); + comp = CreateCompilation([sourceC, s_collectionExtensions], references: [refB]); + comp.VerifyEmitDiagnostics( + // (7,35): error CS0012: The type 'MyCollectionA<>' is defined in an assembly that is not referenced. You must add a reference to assembly '41f5b758-1e64-4c10-88d8-6dd8029c374c, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + // MyCollectionB z = [x, ..y]; + Diagnostic(ErrorCode.ERR_NoTypeDef, "[x, ..y]").WithArguments("MyCollectionA<>", $"{assemblyA}, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(7, 35)); + } - string sourceB2 = """ + [WorkItem("https://github.com/dotnet/roslyn/issues/72898")] + [Fact] + public void Nullable_ConditionalOperator_Error() + { + string sourceA = """ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + [CollectionBuilder(typeof(MyCollectionBuilder), "Create")] + struct MyCollection : IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() => null; + IEnumerator IEnumerable.GetEnumerator() => null; + } + class MyCollectionBuilder + { + public static MyCollection Create(ReadOnlySpan items) => default; + } + """; + string sourceB = """ + #nullable enable + using System.Collections.Generic; class Program { - static void Main() + static IEnumerable F(bool b, MyCollection x, object y) { - MyCollection? x = /**/[[]]/**/; - x.Value.Report(); + return b ? x : [y); } } """; - comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[], "); + var comp = CreateCompilation([sourceB, sourceA], targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (7,26): error CS1003: Syntax error, ',' expected + // return b ? x : [y); + Diagnostic(ErrorCode.ERR_SyntaxError, ")").WithArguments(",").WithLocation(7, 26), + // (7,27): error CS1003: Syntax error, ']' expected + // return b ? x : [y); + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments("]").WithLocation(7, 27)); - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (1 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[[]]') - Elements(1): - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection?[], IsImplicit) (Syntax: '[]') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - ICollectionExpressionOperation (0 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: MyCollection?[]) (Syntax: '[]') - Elements(0) - """); + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var nodes = tree.GetRoot().DescendantNodes(); + var expr = nodes.OfType().Last(); + Assert.Equal("y", expr.ToString()); + _ = model.GetSymbolInfo(expr); + + var conditional = nodes.OfType().Single(); + var info = model.GetTypeInfo(conditional); + Assert.Equal("MyCollection", info.Type.ToTestDisplayString()); } - [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] [Fact] - public void Add_ParamsArray_08() + public void Nullable_SwitchExpression_Error() { string sourceA = """ using System; using System.Collections; using System.Collections.Generic; - class MyCollection : IEnumerable + using System.Runtime.CompilerServices; + [CollectionBuilder(typeof(MyCollectionBuilder), "Create")] + struct MyCollection : IEnumerable { - private List _list = new(); - public void Add(params object[] x) - { - Console.Write("Add: "); - foreach (var i in x) - Console.Write("{0}, ", i); - Console.WriteLine(); - _list.AddRange(x); - } - public IEnumerator GetEnumerator() => _list.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => null; + IEnumerator IEnumerable.GetEnumerator() => null; + } + class MyCollectionBuilder + { + public static MyCollection Create(ReadOnlySpan items) => default; } """; - - string sourceB1 = """ + string sourceB = """ + #nullable enable + using System.Collections.Generic; class Program { - static void Main() + static void F(bool b, MyCollection x, object y) { - object x = 1; - object[] y = [2, 3]; - MyCollection z = /**/[x, ..y]/**/; - z.Report(); + _ = b switch + { + true => x, + false => [y), + }; } } """; - var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: """ - Add: 1, - Add: 2, - Add: 3, - [1, 2, 3], - """); + var comp = CreateCompilation([sourceB, sourceA], targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (10,28): error CS1003: Syntax error, ',' expected + // false => [y), + Diagnostic(ErrorCode.ERR_SyntaxError, ")").WithArguments(",").WithLocation(10, 28), + // (10,30): error CS1003: Syntax error, ']' expected + // false => [y), + Diagnostic(ErrorCode.ERR_SyntaxError, "").WithArguments("]").WithLocation(10, 30)); - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') - Elements(2): - ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Object) (Syntax: 'x') - ISpreadOperation (ElementType: System.Object) (OperationKind.Spread, Type: null) (Syntax: '..y') - Operand: - ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Object[]) (Syntax: 'y') - ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (Identity) - """); + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Last(); + Assert.Equal("y", expr.ToString()); + _ = model.GetSymbolInfo(expr); + } - string sourceB2 = """ + [Fact] + public void Nullable_ArrayInitializer_Error() + { + string sourceA = """ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + [CollectionBuilder(typeof(MyCollectionBuilder), "Create")] + struct MyCollection : IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() => null; + IEnumerator IEnumerable.GetEnumerator() => null; + } + class MyCollectionBuilder + { + public static MyCollection Create(ReadOnlySpan items) => default; + } + """; + string sourceB = """ + #nullable enable + using System.Collections.Generic; class Program { - static void Main() + static void F(MyCollection x, object y) { - object[] x = [1]; - object[][] y = [[2, 3]]; - MyCollection z = /**/[x, ..y]/**/; - z.Report(); + _ = new[] { x, [y) }; } } """; - comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: """ - Add: 1, - Add: 2, 3, - [1, 2, 3], - """); + var comp = CreateCompilation([sourceB, sourceA], targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (7,26): error CS1003: Syntax error, ',' expected + // _ = new[] { x, [y) }; + Diagnostic(ErrorCode.ERR_SyntaxError, ")").WithArguments(",").WithLocation(7, 26), + // (7,28): error CS1003: Syntax error, ']' expected + // _ = new[] { x, [y) }; + Diagnostic(ErrorCode.ERR_SyntaxError, "}").WithArguments("]").WithLocation(7, 28)); - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') - Elements(2): - ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Object[]) (Syntax: 'x') - ISpreadOperation (ElementType: System.Object[]) (OperationKind.Spread, Type: null) (Syntax: '..y') - Operand: - ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Object[][]) (Syntax: 'y') - ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (Identity) - """); + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Last(); + Assert.Equal("y", expr.ToString()); + _ = model.GetSymbolInfo(expr); } - [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] [Fact] - public void Add_ParamsArray_09() + public void Nullable_LambdaExpression_Error() { - string sourceA1 = """ - public abstract class MyCollectionBase - { - public abstract void Add(object[] x); - } - """; - string assemblyName = GetUniqueName(); - var comp = CreateCompilation(new AssemblyIdentity(assemblyName, new Version(1, 0, 0, 0)), sourceA1, references: TargetFrameworkUtil.StandardReferences); - var refA1 = comp.EmitToImageReference(); - - string sourceB = """ + string sourceA = """ + using System; using System.Collections; using System.Collections.Generic; - public class MyCollection : MyCollectionBase, IEnumerable + using System.Runtime.CompilerServices; + [CollectionBuilder(typeof(MyCollectionBuilder), "Create")] + struct MyCollection : IEnumerable { - private List _list = new(); - public override void Add(object[] x) => _list.AddRange(x); - public IEnumerator GetEnumerator() => _list.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => null; + IEnumerator IEnumerable.GetEnumerator() => null; } - """; - comp = CreateCompilation(sourceB, references: [refA1]); - var refB = comp.EmitToImageReference(); - - string sourceA2 = """ - public abstract class MyCollectionBase + class MyCollectionBuilder { - public abstract void Add(params object[] x); + public static MyCollection Create(ReadOnlySpan items) => default; } """; - comp = CreateCompilation(new AssemblyIdentity(assemblyName, new Version(2, 0, 0, 0)), sourceA2, references: TargetFrameworkUtil.StandardReferences); - var refA2 = comp.EmitToImageReference(); - - string sourceC = """ + string sourceB = """ + #nullable enable + using System; + using System.Collections.Generic; class Program { - static void Main() + static void F(MyCollection x, object y) { - object x = 1; - object[] y = [2, 3]; - MyCollection z = /**/[x, ..y]/**/; - z.Report(); + var f = (bool b) => + { + if (b) return x; + return [y); + }; } } """; - comp = CreateCompilation([sourceC, s_collectionExtensions], references: [refA2, refB], options: TestOptions.ReleaseExe); - comp.VerifyEmitDiagnostics(); + var comp = CreateCompilation([sourceB, sourceA], targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (11,26): error CS1003: Syntax error, ',' expected + // return [y); + Diagnostic(ErrorCode.ERR_SyntaxError, ")").WithArguments(",").WithLocation(11, 26), + // (11,27): error CS1003: Syntax error, ']' expected + // return [y); + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments("]").WithLocation(11, 27)); - VerifyOperationTreeForTest(comp, - """ - ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') - Elements(2): - ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Object) (Syntax: 'x') - ISpreadOperation (ElementType: System.Object) (OperationKind.Spread, Type: null) (Syntax: '..y') - Operand: - ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Object[]) (Syntax: 'y') - ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - (Identity) - """); + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Last(); + Assert.Equal("y", expr.ToString()); + _ = model.GetSymbolInfo(expr); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/LockTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/LockTests.cs index deb134acef381..61614ef54b64e 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/LockTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/LockTests.cs @@ -3449,7 +3449,7 @@ IEnumerable M() // (9,15): error CS4007: Instance of type 'System.Threading.Lock.Scope' cannot be preserved across 'await' or 'yield' boundary. // lock (new Lock()) Diagnostic(ErrorCode.ERR_ByRefTypeAndAwait, "new Lock()").WithArguments("System.Threading.Lock.Scope").WithLocation(9, 15), - // (11,13): warning CS9230: 'yield return' should not be used in the body of a lock statement + // (11,13): warning CS9237: 'yield return' should not be used in the body of a lock statement // yield return 2; Diagnostic(ErrorCode.WRN_BadYieldInLock, "yield").WithLocation(11, 13)); } @@ -3561,7 +3561,7 @@ async IAsyncEnumerable M() // (10,15): error CS4007: Instance of type 'System.Threading.Lock.Scope' cannot be preserved across 'await' or 'yield' boundary. // lock (new Lock()) Diagnostic(ErrorCode.ERR_ByRefTypeAndAwait, "new Lock()").WithArguments("System.Threading.Lock.Scope").WithLocation(10, 15), - // (12,13): warning CS9230: 'yield return' should not be used in the body of a lock statement + // (12,13): warning CS9237: 'yield return' should not be used in the body of a lock statement // yield return 2; Diagnostic(ErrorCode.WRN_BadYieldInLock, "yield").WithLocation(12, 13)); } diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs index 36a07cc8f3bd8..472585b36a149 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs @@ -3883,12 +3883,12 @@ void verify(MetadataReference comp1Ref) comp2 = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseDll, parseOptions: TestOptions.Regular12); comp2.VerifyDiagnostics( - // (6,9): error CS8652: The feature 'params collections' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (6,22): error CS1503: Argument 1: cannot convert from 'int' to 'params System.ReadOnlySpan' // Params.Test1(1); - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Params.Test1(1)").WithArguments("params collections").WithLocation(6, 9), - // (9,9): error CS8652: The feature 'params collections' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + Diagnostic(ErrorCode.ERR_BadArgType, "1").WithArguments("1", "int", "params System.ReadOnlySpan").WithLocation(6, 22), + // (9,16): error CS7036: There is no argument given that corresponds to the required parameter 'a' of 'Params.Test1(params ReadOnlySpan)' // Params.Test1(); - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Params.Test1()").WithArguments("params collections").WithLocation(9, 9) + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "Test1").WithArguments("a", "Params.Test1(params System.ReadOnlySpan)").WithLocation(9, 16) ); } } @@ -3950,9 +3950,12 @@ void verify(MetadataReference comp1Ref) comp2 = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseDll, parseOptions: TestOptions.Regular12); comp2.VerifyDiagnostics( - // (6,18): error CS8652: The feature 'params collections' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // var x1 = Params.Test1; - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Params.Test1").WithArguments("params collections").WithLocation(6, 18) + // (9,12): error CS1503: Argument 1: cannot convert from 'int' to 'scoped System.ReadOnlySpan' + // x1(1); + Diagnostic(ErrorCode.ERR_BadArgType, "1").WithArguments("1", "int", "scoped System.ReadOnlySpan").WithLocation(9, 12), + // (12,9): error CS7036: There is no argument given that corresponds to the required parameter 'arg' of '' + // x1(); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "x1").WithArguments("arg", "").WithLocation(12, 9) ); } } @@ -4055,20 +4058,77 @@ void Test2() void verify(MetadataReference comp1Ref) { var comp2 = CreateCompilation(src2, references: [comp1Ref], options: TestOptions.ReleaseDll, parseOptions: TestOptions.RegularPreview); - comp2.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp2, symbolValidator: checkParamsInDelegate1).VerifyDiagnostics(); + + void checkParamsInDelegate1(ModuleSymbol m) + { + Assert.True(m.GlobalNamespace.GetTypeMember("<>f__AnonymousDelegate0").DelegateInvokeMethod.Parameters.Last().IsParams); + } + + var expectedIL = @" +{ + // Code size 65 (0x41) + .maxstack 2 + IL_0000: ldsfld "" Program.<>O.<0>__Test1"" + IL_0005: dup + IL_0006: brtrue.s IL_001b + IL_0008: pop + IL_0009: ldnull + IL_000a: ldftn ""void Params.Test1(params System.Collections.Generic.IEnumerable)"" + IL_0010: newobj ""<>f__AnonymousDelegate0..ctor(object, System.IntPtr)"" + IL_0015: dup + IL_0016: stsfld "" Program.<>O.<0>__Test1"" + IL_001b: call ""void Program.M1<>()"" + IL_0020: ldsfld "" Program.<>O.<0>__Test1"" + IL_0025: dup + IL_0026: brtrue.s IL_003b + IL_0028: pop + IL_0029: ldnull + IL_002a: ldftn ""void Params.Test1(params System.Collections.Generic.IEnumerable)"" + IL_0030: newobj ""<>f__AnonymousDelegate0..ctor(object, System.IntPtr)"" + IL_0035: dup + IL_0036: stsfld "" Program.<>O.<0>__Test1"" + IL_003b: call ""void Program.M1<>()"" + IL_0040: ret +} +"; + verifier.VerifyIL("Program.Test1", expectedIL); comp2 = CreateCompilation(src2, references: [comp1Ref], options: TestOptions.ReleaseDll, parseOptions: TestOptions.RegularNext); - comp2.VerifyDiagnostics(); + verifier = CompileAndVerify(comp2, symbolValidator: checkParamsInDelegate1).VerifyDiagnostics(); + verifier.VerifyIL("Program.Test1", expectedIL); comp2 = CreateCompilation(src2, references: [comp1Ref], options: TestOptions.ReleaseDll, parseOptions: TestOptions.Regular12); - comp2.VerifyDiagnostics( - // (6,17): error CS8652: The feature 'params collections' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // var a = Params.Test1; - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Params.Test1").WithArguments("params collections").WithLocation(6, 17), - // (8,12): error CS8652: The feature 'params collections' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // M1(Params.Test1); - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Params.Test1").WithArguments("params collections").WithLocation(8, 12) - ); + verifier = CompileAndVerify(comp2, symbolValidator: checkParamsInDelegate1).VerifyDiagnostics(); + + // Note, we are using System.Action. which doesn't have params + verifier.VerifyIL("Program.Test1", @" +{ + // Code size 65 (0x41) + .maxstack 2 + IL_0000: ldsfld ""System.Action> Program.<>O.<0>__Test1"" + IL_0005: dup + IL_0006: brtrue.s IL_001b + IL_0008: pop + IL_0009: ldnull + IL_000a: ldftn ""void Params.Test1(params System.Collections.Generic.IEnumerable)"" + IL_0010: newobj ""System.Action>..ctor(object, System.IntPtr)"" + IL_0015: dup + IL_0016: stsfld ""System.Action> Program.<>O.<0>__Test1"" + IL_001b: call ""void Program.M1>>(System.Action>)"" + IL_0020: ldsfld ""System.Action> Program.<>O.<0>__Test1"" + IL_0025: dup + IL_0026: brtrue.s IL_003b + IL_0028: pop + IL_0029: ldnull + IL_002a: ldftn ""void Params.Test1(params System.Collections.Generic.IEnumerable)"" + IL_0030: newobj ""System.Action>..ctor(object, System.IntPtr)"" + IL_0035: dup + IL_0036: stsfld ""System.Action> Program.<>O.<0>__Test1"" + IL_003b: call ""void Program.M1>>(System.Action>)"" + IL_0040: ret +} +"); } } @@ -4163,6 +4223,51 @@ void verify(MetadataReference comp1Ref) } } + [Fact] + [WorkItem("https://github.com/dotnet/csharplang/issues/8061")] + public void LanguageVersion_07_CallSite() + { + var src1 = @" +public class Params +{ + static public void Test1(params System.ReadOnlySpan a) + { + System.Console.Write(""span""); + } + + static public void Test1(params long[] a) + { + System.Console.Write(""array""); + } +} +"; + var src2 = @" +class Program +{ + static void Main() + { + Params.Test1(1); + } +} +"; + var comp1 = CreateCompilation(src1, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseDll); + + verify(comp1.ToMetadataReference()); + verify(comp1.EmitToImageReference()); + + void verify(MetadataReference comp1Ref) + { + var comp2 = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + CompileAndVerify(comp2, expectedOutput: ExpectedOutput("span"), verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + comp2 = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp2, expectedOutput: ExpectedOutput("span"), verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + comp2 = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + CompileAndVerify(comp2, expectedOutput: ExpectedOutput("array"), verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + } + } + [Fact] public void DelegateNaturalType_01() { @@ -5723,6 +5828,52 @@ static void Test(int y, params long[] x) ); } + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72696")] + public void BetterOverload_04_Ambiguity() + { + var src = """ +using System.Collections; +using System.Collections.Generic; + +class MyCollection : IEnumerable +{ + IEnumerator IEnumerable.GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; +} + +static class ExtensionsA +{ + public static void Add(this MyCollection collection, params string[] args) { } +} + +static class ExtensionsB +{ + public static void Add(this MyCollection collection, params string[] args) { } +} + +class Program +{ + static void Main() + { + var x = new MyCollection(); + x.Add(""); + + var y = new MyCollection { "" }; + } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics( + // (25,11): error CS0121: The call is ambiguous between the following methods or properties: 'ExtensionsA.Add(MyCollection, params string[])' and 'ExtensionsB.Add(MyCollection, params string[])' + // x.Add(""); + Diagnostic(ErrorCode.ERR_AmbigCall, "Add").WithArguments("ExtensionsA.Add(MyCollection, params string[])", "ExtensionsB.Add(MyCollection, params string[])").WithLocation(25, 11), + // (27,44): error CS0121: The call is ambiguous between the following methods or properties: 'ExtensionsA.Add(MyCollection, params string[])' and 'ExtensionsB.Add(MyCollection, params string[])' + // var y = new MyCollection { "" }; + Diagnostic(ErrorCode.ERR_AmbigCall, @"""""").WithArguments("ExtensionsA.Add(MyCollection, params string[])", "ExtensionsB.Add(MyCollection, params string[])").WithLocation(27, 44) + ); + } + [Fact] public void GenericInference() { @@ -5806,9 +5957,23 @@ static void Test2(int a, params T[] b) [Fact] public void DynamicInvocation_OrdinaryMethod_02_AmbiguousDynamicParamsArgument() { - var src = """ + var src1 = """ using System.Collections.Generic; +public static class Helpers +{ + public static void Test(params IEnumerable b) + { + System.Console.Write("Called"); + } +} +"""; + + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ +using static Helpers; + class Program { static void Main() @@ -5816,28 +5981,57 @@ static void Main() dynamic d = 1; Test(d); } - - static void Test(params IEnumerable b) - { - System.Console.Write("Called"); - } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + + comp.VerifyDiagnostics( + // (8,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // Test(d); + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Helpers.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 14) + ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); comp.VerifyDiagnostics( // (8,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. // Test(d); - Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 14) + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Helpers.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 14) ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp.VerifyEmitDiagnostics(); } [Fact] public void DynamicInvocation_OrdinaryMethod_03_Warning() { - var src = """ + var src1 = """ using System.Collections.Generic; +public static class Helpers +{ + public static void Test1(params IEnumerable b) => System.Console.Write("Called1"); + public static void Test1(System.DateTime b) => System.Console.Write("Called2"); + + public static void Test2(int x, System.DateTime b) => System.Console.Write("Called3"); + public static void Test2(long x, IEnumerable b) => System.Console.Write("Called4"); + public static void Test2(byte x, params IEnumerable b) => System.Console.Write("Called5"); + + public static void Test3(byte x, params IEnumerable b) => System.Console.Write("Called6"); + public static void Test3(byte x, byte y, byte z) => System.Console.Write("Called7"); + + public static void Test4(byte x, params IEnumerable b) => System.Console.Write("Called8"); + public static void Test4(byte x, long y, long z) => System.Console.Write("Called9"); +} +"""; + + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ +using static Helpers; + class Program { static void Main() @@ -5863,27 +6057,11 @@ static void Main() Test4(d3, x, x); // Called9 Test4(d3, d4, d4); // Called9 } - - static void Test1(params IEnumerable b) => System.Console.Write("Called1"); - static void Test1(System.DateTime b) => System.Console.Write("Called2"); - - static void Test2(int x, System.DateTime b) => System.Console.Write("Called3"); - static void Test2(long x, IEnumerable b) => System.Console.Write("Called4"); - static void Test2(byte x, params IEnumerable b) => System.Console.Write("Called5"); - - static void Test3(byte x, params IEnumerable b) => System.Console.Write("Called6"); - static void Test3(byte x, byte y, byte z) => System.Console.Write("Called7"); - - static void Test4(byte x, params IEnumerable b) => System.Console.Write("Called8"); - static void Test4(byte x, long y, long z) => System.Console.Write("Called9"); } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). - VerifyDiagnostics( + var expected = new[] { // (8,9): warning CS9220: One or more overloads of method 'Test1' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // Test1(d1); // Called2 Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionMethod, "Test1(d1)").WithArguments("Test1").WithLocation(8, 9), @@ -5905,7 +6083,62 @@ static void Main() // (26,9): warning CS9220: One or more overloads of method 'Test4' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // Test4(d3, d4, d4); // Called9 Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionMethod, "Test4(d3, d4, d4)").WithArguments("Test4").WithLocation(26, 9) + }; + + CompileAndVerify( + comp, + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). + VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + CompileAndVerify( + comp, + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). + VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + comp.VerifyDiagnostics( + // (21,19): error CS1503: Argument 2: cannot convert from 'int' to 'byte' + // Test3(d3, x, x); // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("2", "int", "byte").WithLocation(21, 19), + // (21,22): error CS1503: Argument 3: cannot convert from 'int' to 'byte' + // Test3(d3, x, x); // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("3", "int", "byte").WithLocation(21, 22) ); + + var src3 = """ +using static Helpers; + +class Program +{ + static void Main() + { + dynamic d1 = System.DateTime.Now; + Test1(d1); // Called2 + + dynamic d2 = new[] { 1 }; + Test1(d2); // Called1 + Test2(1, d1); // Called3 + Test2(1, d2); // Called5 + + int x = 1; + Test2(x, d1); // Called3 + Test2(x, d2); // Called4 + + dynamic d3 = (byte)1; + + dynamic d4 = x; + Test4(d3, x, x); // Called9 + Test4(d3, d4, d4); // Called9 + } +} +"""; + + comp = CreateCompilation(src3, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + CompileAndVerify( + comp, + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called9Called9"). + VerifyDiagnostics(); } [Fact] @@ -5969,29 +6202,59 @@ static void Test(params IEnumerable b) [Fact] public void DynamicInvocation_OrdinaryMethod_06_TypeArgumentInferenceError() { - var src1 = """ + var src0 = """ using System.Collections.Generic; -class Program +public class Program { - static void Main() + public static void Test(params IEnumerable b) { - dynamic d = 1; - Test(d, 2, 3); } +} +"""; + var comp0Ref = CreateCompilation(src0).EmitToImageReference(); - static void Test(params IEnumerable b) + var src1 = """ +using static Program; + +class P +{ + static void Main() { - System.Console.Write("Called"); + dynamic d = 1; + Test(d, 2, 3); + Test(d); } } """; - var comp1 = CreateCompilation(src1, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + var comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + + comp1.VerifyDiagnostics( + // (8,9): error CS9218: The type arguments for method 'Program.Test(params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + // Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(d, 2, 3)").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9), + // (9,9): error CS9218: The type arguments for method 'Program.Test(params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + // Test(d); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(d)").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(9, 9) + ); + + comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); comp1.VerifyDiagnostics( // (8,9): error CS9218: The type arguments for method 'Program.Test(params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. // Test(d, 2, 3); - Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(d, 2, 3)").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(d, 2, 3)").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9), + // (9,9): error CS9218: The type arguments for method 'Program.Test(params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + // Test(d); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(d)").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(9, 9) + ); + + comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp1.VerifyEmitDiagnostics( + // (8,9): error CS1501: No overload for method 'Test' takes 3 arguments + // Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_BadArgCount, "Test").WithArguments("Test", "3").WithLocation(8, 9) ); var src2 = """ @@ -6021,29 +6284,59 @@ static void Test(params IEnumerable b) [Fact] public void DynamicInvocation_OrdinaryMethod_07_TypeArgumentInferenceError() { - var src1 = """ + var src0 = """ using System.Collections.Generic; -class Program +public class Program { - static void Main() + public static void Test(T a, params IEnumerable b) { - dynamic d = 1; - Test(0, d, 2, 3); } +} +"""; + var comp0Ref = CreateCompilation(src0).EmitToImageReference(); - static void Test(T a, params IEnumerable b) + var src1 = """ +using static Program; + +class P +{ + static void Main() { - System.Console.Write("Called"); + dynamic d = 1; + Test(0, d, 2, 3); + Test(0, d); } } """; - var comp1 = CreateCompilation(src1, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + var comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); comp1.VerifyDiagnostics( // (8,9): error CS9218: The type arguments for method 'Program.Test(T, params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. // Test(0, d, 2, 3); - Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(0, d, 2, 3)").WithArguments("Program.Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(0, d, 2, 3)").WithArguments("Program.Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9), + // (9,9): error CS9218: The type arguments for method 'Program.Test(T, params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + // Test(0, d); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(0, d)").WithArguments("Program.Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(9, 9) + ); + + comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + + comp1.VerifyDiagnostics( + // (8,9): error CS9218: The type arguments for method 'Program.Test(T, params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + // Test(0, d, 2, 3); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(0, d, 2, 3)").WithArguments("Program.Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9), + // (9,9): error CS9218: The type arguments for method 'Program.Test(T, params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + // Test(0, d); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(0, d)").WithArguments("Program.Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(9, 9) + ); + + comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp1.VerifyEmitDiagnostics( + // (8,9): error CS1501: No overload for method 'Test' takes 4 arguments + // Test(0, d, 2, 3); + Diagnostic(ErrorCode.ERR_BadArgCount, "Test").WithArguments("Test", "4").WithLocation(8, 9) ); var src2 = """ @@ -6604,7 +6897,14 @@ void Test2(int a, int[] b) [Fact] public void DynamicInvocation_Delegate_02_AmbiguousDynamicParamsArgument() { - var src = """ + var src1 = """ +using System.Collections.Generic; + +public delegate void D(params IEnumerable b); +"""; + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ using System.Collections.Generic; class Program @@ -6621,16 +6921,26 @@ static void Test(IEnumerable b) } } } - -delegate void D(params IEnumerable b); """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + + comp.VerifyDiagnostics( + // (9,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'D.Invoke(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // test(d); + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("D.Invoke(params System.Collections.Generic.IEnumerable)").WithLocation(9, 14) + ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); comp.VerifyDiagnostics( // (9,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'D.Invoke(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. // test(d); Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("D.Invoke(params System.Collections.Generic.IEnumerable)").WithLocation(9, 14) ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp.VerifyEmitDiagnostics(); } [Fact] @@ -6697,42 +7007,160 @@ class C2 [Fact] public void DynamicInvocation_Indexer_02_AmbiguousDynamicParamsArgument() { - var src = """ + var src1 = """ using System.Collections.Generic; -class Program +public class Program +{ + public int this[params IEnumerable b] + { + get + { + System.Console.Write("Called"); + return 0; + } + } +} +"""; + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ +class P { static void Main() { dynamic d = 1; _ = new Program()[d]; } +} +"""; + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); - int this[params IEnumerable b] - { - get - { - System.Console.Write("Called"); - return 0; + comp.VerifyDiagnostics( + // (6,27): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.this[params IEnumerable]', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // _ = new Program()[d]; + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.this[params System.Collections.Generic.IEnumerable]").WithLocation(6, 27) + ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + + comp.VerifyDiagnostics( + // (6,27): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.this[params IEnumerable]', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // _ = new Program()[d]; + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.this[params System.Collections.Generic.IEnumerable]").WithLocation(6, 27) + ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp.VerifyEmitDiagnostics(); } + + [Fact] + public void DynamicInvocation_Indexer_03_Warning() + { + var src1 = """ +using System.Collections.Generic; + +public class Test1 +{ + public int this[params IEnumerable b] { get { System.Console.Write("Called1"); return 0; } } + public int this[System.DateTime b] { get { System.Console.Write("Called2"); return 0; } } +} +public class Test2 +{ + public int this[int x, System.DateTime b] { get { System.Console.Write("Called3"); return 0; } } + public int this[long x, IEnumerable b] { get { System.Console.Write("Called4"); return 0; } } + public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called5"); return 0; } } +} +public class Test3 +{ + public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called6"); return 0; } } + public int this[byte x, byte y, byte z] { get { System.Console.Write("Called7"); return 0; } } +} +public class Test4 +{ + public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called8"); return 0; } } + public int this[byte x, long y, long z] { get { System.Console.Write("Called9"); return 0; } } +} +"""; + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ +class Program +{ + static void Main() + { + dynamic d1 = System.DateTime.Now; + _ = new Test1()[d1]; // Called2 + + dynamic d2 = new[] { 1 }; + _ = new Test1()[d2]; // Called1 + _ = new Test2()[1, d1]; // Called3 + _ = new Test2()[1, d2]; // Called5 + + int x = 1; + _ = new Test2()[x, d1]; // Called3 + _ = new Test2()[x, d2]; // Called4 + + dynamic d3 = (byte)1; + _ = new Test3()[d3, 1, 2]; // Called7 + _ = new Test3()[d3, x, x]; // Called6 + + dynamic d4 = x; + _ = new Test4()[(byte)d3, x, x]; // Called8 + _ = new Test4()[d3, x, x]; // Called9 + _ = new Test4()[d3, d4, d4]; // Called9 } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + + var expected = new[] { + // (6,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test1()[d1]; // Called2 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test1()[d1]").WithLocation(6, 13), + // (9,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test1()[d2]; // Called1 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test1()[d2]").WithLocation(9, 13), + // (10,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test2()[1, d1]; // Called3 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test2()[1, d1]").WithLocation(10, 13), + // (11,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test2()[1, d2]; // Called5 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test2()[1, d2]").WithLocation(11, 13), + // (18,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test3()[d3, 1, 2]; // Called7 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test3()[d3, 1, 2]").WithLocation(18, 13), + // (23,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test4()[d3, x, x]; // Called9 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test4()[d3, x, x]").WithLocation(23, 13), + // (24,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test4()[d3, d4, d4]; // Called9 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test4()[d3, d4, d4]").WithLocation(24, 13) + }; + + CompileAndVerify( + comp, + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). + VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + CompileAndVerify( + comp, + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). + VerifyDiagnostics(expected); + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); comp.VerifyDiagnostics( - // (8,27): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.this[params IEnumerable]', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. - // _ = new Program()[d]; - Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.this[params System.Collections.Generic.IEnumerable]").WithLocation(8, 27) + // (19,29): error CS1503: Argument 2: cannot convert from 'int' to 'byte' + // _ = new Test3()[d3, x, x]; // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("2", "int", "byte").WithLocation(19, 29), + // (19,32): error CS1503: Argument 3: cannot convert from 'int' to 'byte' + // _ = new Test3()[d3, x, x]; // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("3", "int", "byte").WithLocation(19, 32) ); - } - - [Fact] - public void DynamicInvocation_Indexer_03_Warning() - { - var src = """ -using System.Collections.Generic; + var src3 = """ class Program { static void Main() @@ -6750,66 +7178,19 @@ static void Main() _ = new Test2()[x, d2]; // Called4 dynamic d3 = (byte)1; - _ = new Test3()[d3, 1, 2]; // Called7 - _ = new Test3()[d3, x, x]; // Called6 dynamic d4 = x; - _ = new Test4()[(byte)d3, x, x]; // Called8 _ = new Test4()[d3, x, x]; // Called9 _ = new Test4()[d3, d4, d4]; // Called9 } - - class Test1 - { - public int this[params IEnumerable b] { get { System.Console.Write("Called1"); return 0; } } - public int this[System.DateTime b] { get { System.Console.Write("Called2"); return 0; } } - } - class Test2 - { - public int this[int x, System.DateTime b] { get { System.Console.Write("Called3"); return 0; } } - public int this[long x, IEnumerable b] { get { System.Console.Write("Called4"); return 0; } } - public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called5"); return 0; } } - } - class Test3 - { - public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called6"); return 0; } } - public int this[byte x, byte y, byte z] { get { System.Console.Write("Called7"); return 0; } } - } - class Test4 - { - public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called8"); return 0; } } - public int this[byte x, long y, long z] { get { System.Console.Write("Called9"); return 0; } } - } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + comp = CreateCompilation(src3, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); CompileAndVerify( comp, - expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). - VerifyDiagnostics( - // (8,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test1()[d1]; // Called2 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test1()[d1]").WithLocation(8, 13), - // (11,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test1()[d2]; // Called1 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test1()[d2]").WithLocation(11, 13), - // (12,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test2()[1, d1]; // Called3 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test2()[1, d1]").WithLocation(12, 13), - // (13,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test2()[1, d2]; // Called5 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test2()[1, d2]").WithLocation(13, 13), - // (20,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test3()[d3, 1, 2]; // Called7 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test3()[d3, 1, 2]").WithLocation(20, 13), - // (25,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test4()[d3, x, x]; // Called9 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test4()[d3, x, x]").WithLocation(25, 13), - // (26,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test4()[d3, d4, d4]; // Called9 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test4()[d3, d4, d4]").WithLocation(26, 13) - ); + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called9Called9"). + VerifyDiagnostics(); } [Fact] @@ -7232,9 +7613,21 @@ public Test2(int a, params int[] b) [Fact] public void DynamicInvocation_Constructor_02_AmbiguousDynamicParamsArgument() { - var src = """ + var src1 = """ using System.Collections.Generic; +public class Test +{ + public Test(params IEnumerable b) + { + System.Console.Write("Called"); + } +} +"""; + + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ class Program { static void Main() @@ -7242,31 +7635,62 @@ static void Main() dynamic d = 1; new Test(d); } - - class Test - { - public Test(params IEnumerable b) - { - System.Console.Write("Called"); - } - } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + + comp.VerifyDiagnostics( + // (6,18): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Test.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // new Test(d); + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Test.Test(params System.Collections.Generic.IEnumerable)").WithLocation(6, 18) + ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); comp.VerifyDiagnostics( - // (8,18): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.Test.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // (6,18): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Test.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. // new Test(d); - Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.Test.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 18) + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Test.Test(params System.Collections.Generic.IEnumerable)").WithLocation(6, 18) ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp.VerifyEmitDiagnostics(); } [Fact] public void DynamicInvocation_Constructor_03_Warning() { - var src = """ + var src1 = """ using System.Collections.Generic; +public class Test1 +{ + public Test1(params IEnumerable b) => System.Console.Write("Called1"); + public Test1(System.DateTime b) => System.Console.Write("Called2"); +} + +public class Test2 +{ + public Test2(int x, System.DateTime b) => System.Console.Write("Called3"); + public Test2(long x, IEnumerable b) => System.Console.Write("Called4"); + public Test2(byte x, params IEnumerable b) => System.Console.Write("Called5"); +} + +public class Test3 +{ + public Test3(byte x, params IEnumerable b) => System.Console.Write("Called6"); + public Test3(byte x, byte y, byte z) => System.Console.Write("Called7"); +} + +public class Test4 +{ + public Test4(byte x, params IEnumerable b) => System.Console.Write("Called8"); + public Test4(byte x, long y, long z) => System.Console.Write("Called9"); +} +"""; + + var src2 = """ class Program { static void Main() @@ -7292,61 +7716,90 @@ static void Main() new Test4(d3, x, x); // Called9 new Test4(d3, d4, d4); // Called9 } - - class Test1 - { - public Test1(params IEnumerable b) => System.Console.Write("Called1"); - public Test1(System.DateTime b) => System.Console.Write("Called2"); - } - - class Test2 - { - public Test2(int x, System.DateTime b) => System.Console.Write("Called3"); - public Test2(long x, IEnumerable b) => System.Console.Write("Called4"); - public Test2(byte x, params IEnumerable b) => System.Console.Write("Called5"); - } - - class Test3 - { - public Test3(byte x, params IEnumerable b) => System.Console.Write("Called6"); - public Test3(byte x, byte y, byte z) => System.Console.Write("Called7"); - } - - class Test4 - { - public Test4(byte x, params IEnumerable b) => System.Console.Write("Called8"); - public Test4(byte x, long y, long z) => System.Console.Write("Called9"); - } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). - VerifyDiagnostics( - // (8,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + + var expected = new[] { + // (6,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test1(d1); // Called2 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test1(d1)").WithLocation(8, 9), - // (11,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test1(d1)").WithLocation(6, 9), + // (9,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test1(d2); // Called1 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test1(d2)").WithLocation(11, 9), - // (12,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test1(d2)").WithLocation(9, 9), + // (10,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test2(1, d1); // Called3 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test2(1, d1)").WithLocation(12, 9), - // (13,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test2(1, d1)").WithLocation(10, 9), + // (11,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test2(1, d2); // Called5 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test2(1, d2)").WithLocation(13, 9), - // (20,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test2(1, d2)").WithLocation(11, 9), + // (18,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test3(d3, 1, 2); // Called7 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test3(d3, 1, 2)").WithLocation(20, 9), - // (25,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test3(d3, 1, 2)").WithLocation(18, 9), + // (23,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test4(d3, x, x); // Called9 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test4(d3, x, x)").WithLocation(25, 9), - // (26,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test4(d3, x, x)").WithLocation(23, 9), + // (24,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test4(d3, d4, d4); // Called9 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test4(d3, d4, d4)").WithLocation(26, 9) + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test4(d3, d4, d4)").WithLocation(24, 9) + }; + + CompileAndVerify( + comp, + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). + VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + CompileAndVerify( + comp, + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). + VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + comp.VerifyDiagnostics( + // (19,23): error CS1503: Argument 2: cannot convert from 'int' to 'byte' + // new Test3(d3, x, x); // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("2", "int", "byte").WithLocation(19, 23), + // (19,26): error CS1503: Argument 3: cannot convert from 'int' to 'byte' + // new Test3(d3, x, x); // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("3", "int", "byte").WithLocation(19, 26) ); + + var src3 = """ +class Program +{ + static void Main() + { + dynamic d1 = System.DateTime.Now; + new Test1(d1); // Called2 + + dynamic d2 = new[] { 1 }; + new Test1(d2); // Called1 + new Test2(1, d1); // Called3 + new Test2(1, d2); // Called5 + + int x = 1; + new Test2(x, d1); // Called3 + new Test2(x, d2); // Called4 + + dynamic d3 = (byte)1; + + dynamic d4 = x; + new Test4(d3, x, x); // Called9 + new Test4(d3, d4, d4); // Called9 + } +} +"""; + + comp = CreateCompilation(src3, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + CompileAndVerify( + comp, + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called9Called9"). + VerifyDiagnostics(); } [Fact] @@ -14880,5 +15333,164 @@ static void M2(params IEnumerable x) Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "M2").WithArguments("M2", "C1").WithLocation(16, 17) ); } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72098")] + [Fact] + public void AddMethod_Derived_01() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + + class Element { } + + class ElementCollection : IEnumerable + { + private readonly List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + public void Add(Element element) { _list.Add(element); } + } + + class Program + { + static void Main() + { + Test(new Element(), null); + } + + static void Test(params ElementCollection c) + { + c.Report(); + } + } + """; + CompileAndVerify([source, CollectionExpressionTests.s_collectionExtensions], expectedOutput: "[Element, null], "); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72098")] + [Fact] + public void AddMethod_Derived_02() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + + class Base { } + class Element : Base { } + + class ElementCollection : IEnumerable + { + private readonly List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Add(Element element) { _list.Add(element); } + } + + class Program + { + static void Main() + { + Test(new Element(), null); + } + + static void Test(params ElementCollection c) + { + c.Report(); + } + } + """; + CompileAndVerify([source, CollectionExpressionTests.s_collectionExtensions], expectedOutput: "[Element, null], "); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/71240")] + [Fact] + public void AddMethod_Derived_03() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + + class Sample : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(T t) { if (t is object[] o) _list.Add(o); } + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + Test(["a"], ["b"], ["c"]); + } + + static void Test(params Sample s) + { + s.Report(); + } + } + """; + CompileAndVerify([sourceA, sourceB1, CollectionExpressionTests.s_collectionExtensions], expectedOutput: "[[a], [b], [c]], "); + + string sourceB2 = """ + class Program + { + static void Main() + { + Test("a", null); + } + + static void Test(params Sample s) + { + } + } + """; + var comp = CreateCompilation([sourceA, sourceB2]); + comp.VerifyEmitDiagnostics( + // (5,14): error CS1503: Argument 1: cannot convert from 'string' to 'object[]' + // Test("a", null); + Diagnostic(ErrorCode.ERR_BadArgType, @"""a""").WithArguments("1", "string", "object[]").WithLocation(5, 14) + ); + } + + [Fact] + public void AddMethod_Generic_02() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private readonly List _list = new(); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public void Add(T t) { _list.Add(t); } + } + class Program + { + + static void Test(params MyCollection z) + { + } + + static void Main() + { + int x = 1; + Test(x); + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (12,22): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. + // static void Test(params MyCollection z) + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "params MyCollection z").WithArguments("MyCollection").WithLocation(12, 22), + // (19,14): error CS1503: Argument 1: cannot convert from 'int' to 'params MyCollection' + // Test(x); + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "int", "params MyCollection").WithLocation(19, 14) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests3.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests3.cs index 27bd784d69384..fc00e0c57b893 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests3.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests3.cs @@ -1393,7 +1393,7 @@ static void Main() { } }"; var expectedDiagnostics = new DiagnosticDescription[] { - // (4,19): warning CS8618: Non-nullable field 'o' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (4,19): warning CS8618: Non-nullable field 'o' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // static object o; Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "o").WithArguments("field", "o").WithLocation(4, 19), // (4,19): warning CS0649: Field 'C.o' is never assigned to, and will always have its default value null diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs index bd9fcd94491c7..6cdc9572dc7d3 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs @@ -7117,7 +7117,10 @@ struct Example() Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "ReadOnlySpan").WithArguments("System.ReadOnlySpan").WithLocation(5, 12), // (5,50): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50)); + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50), + // (5,50): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope + // public ReadOnlySpan Property { get; } = stackalloc int[512]; + Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(5, 50)); } public static IEnumerable ParameterScope_MemberData() @@ -19668,7 +19671,7 @@ internal class MyOtherClass // (57,32): warning CS0067: The event 'MyOtherClass.SomethingChanged' is never used // public event EventHandler? SomethingChanged; Diagnostic(ErrorCode.WRN_UnreferencedEvent, "SomethingChanged").WithArguments("MyOtherClass.SomethingChanged").WithLocation(57, 32), - // (58,19): warning CS8618: Non-nullable property 'MyProperty' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (58,19): warning CS8618: Non-nullable property 'MyProperty' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public string MyProperty { get; set; } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "MyProperty").WithArguments("property", "MyProperty").WithLocation(58, 19) ); diff --git a/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs b/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs index ccc34c3e368d9..64cc4a4f131e7 100644 --- a/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs +++ b/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs @@ -172,7 +172,7 @@ void M2() { // This test is a canary attempting to make sure that we don't regress the # of fluent calls that // the compiler can handle. - [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1874763")] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/72678"), WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1874763")] public void OverflowOnFluentCall_ExtensionMethods() { int numberFluentCalls = (IntPtr.Size, ExecutionConditionUtil.Configuration, RuntimeUtilities.IsDesktopRuntime, RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) switch diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFixedStatement.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFixedStatement.cs index 0090415273ffb..26daa53a5bf31 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFixedStatement.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFixedStatement.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; +using System.Linq; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests @@ -599,7 +600,6 @@ unsafe void M(bool b) Statements (0) Next (Regular) Block[B1] Entering: {R1} {R2} - .locals {R1} { Locals: [System.Int32* p] @@ -611,89 +611,88 @@ unsafe void M(bool b) Statements (0) Jump if False (Regular) to Block[B3] IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') - Next (Regular) Block[B2] Block[B2] - Block Predecessors: [B1] Statements (1) IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '&i1') - Value: + Value: IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*, IsInvalid) (Syntax: '&i1') - Reference: + Reference: IFieldReferenceOperation: System.Int32 MyClass.i1 (OperationKind.FieldReference, Type: System.Int32, IsInvalid) (Syntax: 'i1') - Instance Receiver: + Instance Receiver: IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: MyClass, IsInvalid, IsImplicit) (Syntax: 'i1') - Next (Regular) Block[B4] Block[B3] - Block Predecessors: [B1] Statements (1) IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '&i2') - Value: + Value: IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*, IsInvalid) (Syntax: '&i2') - Reference: + Reference: IFieldReferenceOperation: System.Int32 MyClass.i2 (OperationKind.FieldReference, Type: System.Int32, IsInvalid) (Syntax: 'i2') - Instance Receiver: + Instance Receiver: IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: MyClass, IsInvalid, IsImplicit) (Syntax: 'i2') - Next (Regular) Block[B4] Block[B4] - Block Predecessors: [B2] [B3] Statements (1) ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32*, IsInvalid, IsImplicit) (Syntax: 'p = b ? &i1 : &i2') - Left: + Left: ILocalReferenceOperation: p (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32*, IsInvalid, IsImplicit) (Syntax: 'p = b ? &i1 : &i2') - Right: - IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: ?, IsInvalid, IsImplicit) (Syntax: 'b ? &i1 : &i2') - + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32*, IsInvalid, IsImplicit) (Syntax: 'b ? &i1 : &i2') Next (Regular) Block[B5] Leaving: {R2} } - Block[B5] - Block Predecessors: [B4] Statements (0) Jump if False (Regular) to Block[B7] IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') Leaving: {R1} - Next (Regular) Block[B6] Block[B6] - Block Predecessors: [B5] Statements (1) IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Cons ... is {*p}"");') - Expression: + Expression: IInvocationOperation (void System.Console.WriteLine(System.String value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Cons ... P is {*p}"")') - Instance Receiver: + Instance Receiver: null Arguments(1): IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: '$""P is {*p}""') IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""P is {*p}""') Parts(2): IInterpolatedStringTextOperation (OperationKind.InterpolatedStringText, Type: null) (Syntax: 'P is ') - Text: + Text: ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""P is "", IsImplicit) (Syntax: 'P is ') IInterpolationOperation (OperationKind.Interpolation, Type: null) (Syntax: '{*p}') - Expression: + Expression: IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '*p') Children(1): ILocalReferenceOperation: p (OperationKind.LocalReference, Type: System.Int32*) (Syntax: 'p') - Alignment: + Alignment: null - FormatString: + FormatString: 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) - Next (Regular) Block[B7] Leaving: {R1} } - Block[B7] - Exit Predecessors: [B5] [B6] Statements (0) "; - VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics, compilationOptions: TestOptions.UnsafeDebugDll); + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll); + VerifyFlowGraphAndDiagnosticsForTest(comp, expectedFlowGraph, expectedDiagnostics); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single(); + var info = model.GetTypeInfo(expr); + Assert.Equal("System.Int32*", info.Type.ToTestDisplayString()); } [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs index ebde2d102e21d..a382338ea9d86 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs @@ -15000,6 +15000,42 @@ static void M(bool result) VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/72931")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72931")] + public void ObjectCreationFlow_75_CollectionInitializerError() + { + string source = @" +public class C +{ + public static void Main() + /**/{ + int d = 1; + var c = new C() { [d] = {2} }; + }/**/ + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + } +} + +class C2 +{ +} +"; + var expectedDiagnostics = new[] { + // (7,33): error CS1922: Cannot initialize type 'C2' with a collection initializer because it does not implement 'System.Collections.IEnumerable' + // var c = new C() { [d] = {2} }; + Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{2}").WithArguments("C2").WithLocation(7, 33) + }; + + string expectedFlowGraph = @" +"; + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); + } + [Fact] public void ObjectCreationExpression_NoNewConstraint() { diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs index 766404d3df5d2..f784b9712c03f 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs @@ -7889,6 +7889,118 @@ Element Values(0) VerifyFlowGraphAndDiagnosticsForTest(source + s_IAsyncEnumerable + IOperationTests_IForEachLoopStatement.s_ValueTask, expectedGraph, expectedDiagnostics); } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")] + public void UsingDeclaration_Flow_25() + { + var code = """ + class C + { + void M() + /**/{ + x: + System.Action a = () => { + using System.IDisposable d = null; + goto x; + }; + }/**/ + + } + """; + var expectedGraph = """ +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Action a] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Action, IsInvalid, IsImplicit) (Syntax: 'a = () => { ... }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Action, IsInvalid, IsImplicit) (Syntax: 'a = () => { ... }') + Right: + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Action, IsInvalid, IsImplicit) (Syntax: '() => { ... }') + Target: + IFlowAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.FlowAnonymousFunction, Type: null, IsInvalid) (Syntax: '() => { ... }') + { + Block[B0#A0] - Entry + Statements (0) + Next (Regular) Block[B1#A0] + Entering: {R1#A0} + .locals {R1#A0} + { + Locals: [System.IDisposable d] + Block[B1#A0] - Block + Predecessors: [B0#A0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null') + Left: + ILocalReferenceOperation: d (IsDeclaration: True) (OperationKind.LocalReference, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null') + Right: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, 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') + Next (Regular) Block[B2#A0] + Entering: {R2#A0} {R3#A0} + .try {R2#A0, R3#A0} + { + Block[B2#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Error) Block[null] + } + .finally {R4#A0} + { + Block[B3#A0] - Block + Predecessors (0) + Statements (0) + Jump if True (Regular) to Block[B5#A0] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'd = null') + Operand: + ILocalReferenceOperation: d (OperationKind.LocalReference, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null') + Next (Regular) Block[B4#A0] + Block[B4#A0] - Block + Predecessors: [B3#A0] + Statements (1) + IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'd = null') + Instance Receiver: + ILocalReferenceOperation: d (OperationKind.LocalReference, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null') + Arguments(0) + Next (Regular) Block[B5#A0] + Block[B5#A0] - Block + Predecessors: [B3#A0] [B4#A0] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } + } + Block[B6#A0] - Exit [UnReachable] + Predecessors (0) + Statements (0) + } + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"""; + var expectedDiagnostics = new[] + { + // (5,9): warning CS0164: This label has not been referenced + // x: + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "x").WithLocation(5, 9), + // (8,13): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(8, 13) + }; + VerifyFlowGraphAndDiagnosticsForTest(code, expectedGraph, expectedDiagnostics); + } + [CompilerTrait(CompilerFeature.IOperation)] [Fact, WorkItem(32100, "https://github.com/dotnet/roslyn/issues/32100")] public void UsingDeclaration_SingleDeclaration() diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs index c965d1742584e..0db28e559375b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs @@ -13,11 +13,32 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { - public partial class SyntaxBinderTests + public partial class DynamicTests : CompilingTestBase { + private static void TestTypes(string source) + { + SyntaxBinderTests.TestTypes(source); + } + + private static void TestOperatorKinds(string source) + { + SyntaxBinderTests.TestOperatorKinds(source); + } + + private static void TestDynamicMemberAccessCore(string source) + { + SyntaxBinderTests.TestDynamicMemberAccessCore(source); + } + + private static void TestCompoundAssignment(string source) + { + SyntaxBinderTests.TestCompoundAssignment(source); + } + #region Conversions [Fact] @@ -3013,7 +3034,7 @@ public C1(int x){} public C1(long x){} } "; - CreateCompilationWithMscorlib40AndSystemCore(new[] { Parse(source, options: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp5)) }).VerifyDiagnostics( + CreateCompilationWithMscorlib40AndSystemCore(new[] { Parse(source, options: TestOptions.RegularPreview) }).VerifyDiagnostics( // (43,55): warning CS1981: Using 'is' to test compatibility with 'dynamic' is essentially identical to testing compatibility with 'Object' and will succeed for all non-null values // Expression> e18 = x => d is dynamic; // ok, warning @@ -3079,6 +3100,76 @@ public C1(long x){} // Expression> e24 = x => new C1(x); Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "new C1(x)").WithLocation(49, 55) ); + + CreateCompilationWithMscorlib40AndSystemCore(new[] { Parse(source, options: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp5)) }).VerifyDiagnostics( + + // (43,55): warning CS1981: Using 'is' to test compatibility with 'dynamic' is essentially identical to testing compatibility with 'Object' and will succeed for all non-null values + // Expression> e18 = x => d is dynamic; // ok, warning + Diagnostic(ErrorCode.WRN_IsDynamicIsConfusing, "d is dynamic").WithArguments("is", "dynamic", "Object").WithLocation(43, 55), + // (46,59): error CS8382: Invalid object creation + // Expression> e21 = x => new dynamic(); + Diagnostic(ErrorCode.ERR_InvalidObjectCreation, "dynamic").WithLocation(46, 59), + // (25,52): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e0 = () => new C { P = d }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(25, 52), + // (27,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e2 = () => new C { D = { X = { Y = 1 }, Z = 1 } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "X").WithLocation(27, 54), + // (27,60): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e2 = () => new C { D = { X = { Y = 1 }, Z = 1 } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "Y").WithLocation(27, 60), + // (27,69): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e2 = () => new C { D = { X = { Y = 1 }, Z = 1 } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "Z").WithLocation(27, 69), + // (28,44): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e3 = () => new C() { { d }, { d, d, d } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "{ d }").WithLocation(28, 44), + // (28,51): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e3 = () => new C() { { d }, { d, d, d } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "{ d, d, d }").WithLocation(28, 51), + // (29,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e4 = x => x.goo(); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.goo()").WithLocation(29, 54), + // (29,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e4 = x => x.goo(); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.goo").WithLocation(29, 54), + // (30,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e5 = x => x[1]; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x[1]").WithLocation(30, 54), + // (31,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e6 = x => x.y.z; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.y.z").WithLocation(31, 54), + // (31,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e6 = x => x.y.z; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.y").WithLocation(31, 54), + // (32,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e7 = x => x + 1; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x + 1").WithLocation(32, 54), + // (33,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e8 = x => -x; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "-x").WithLocation(33, 54), + // (34,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e9 = x => f(d); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f(d)").WithLocation(34, 54), + // (36,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e11 = x => f((dynamic)1); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f((dynamic)1)").WithLocation(36, 55), + // (37,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e12 = x => f(d ?? null); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f(d ?? null)").WithLocation(37, 55), + // (38,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e13 = x => d ? 1 : 2; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(38, 55), + // (39,56): error CS1989: Async lambda expressions cannot be converted to expression trees + // Expression>> e14 = async x => await d; + Diagnostic(ErrorCode.ERR_BadAsyncExpressionTree, "async x => await d").WithLocation(39, 56), + // (47,84): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e22 = x => from a in new[] { d } select a + 1; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "a + 1").WithLocation(47, 84), + // (49,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e24 = x => new C1(x); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "new C1(x)").WithLocation(49, 55) + ); } [Fact, WorkItem(578401, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/578401")] @@ -4399,13 +4490,24 @@ static void Main() } "; - var comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.Regular7_2, options: TestOptions.DebugExe); + var comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); CompileAndVerify(comp, expectedOutput: @" True True ").VerifyDiagnostics(); + + comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.Regular7_2, options: TestOptions.DebugExe); + + comp.VerifyEmitDiagnostics( + // (10,15): error CS8364: Arguments with 'in' modifier cannot be used in dynamically dispatched expressions. + // M1(in d, d = 2, in d); + Diagnostic(ErrorCode.ERR_InDynamicMethodArg, "d").WithLocation(10, 15), + // (10,28): error CS8364: Arguments with 'in' modifier cannot be used in dynamically dispatched expressions. + // M1(in d, d = 2, in d); + Diagnostic(ErrorCode.ERR_InDynamicMethodArg, "d").WithLocation(10, 28) + ); } [WorkItem(22813, "https://github.com/dotnet/roslyn/issues/22813")] @@ -4545,5 +4647,7317 @@ class C2 : C1 CompileAndVerify(comp, expectedOutput: "int").VerifyDiagnostics(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + public void RefStructReceiver01() + { + var code = """ + var s = new S(); + dynamic d = null; + + s.M(d); + + ref struct S + { + public void M(T t) { } + } + """; + + CreateCompilation(code).VerifyDiagnostics( + // (4,1): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s.M(d); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(4, 1) + ); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("object")] + [InlineData("dynamic")] + public void RefStructReceiver02(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + s.M(d); + + ref struct S + { + public void M({{argType}} o) => System.Console.WriteLine(o); + } + """; + + var verifier = CompileAndVerify(code, expectedOutput: "Hello world", targetFramework: TargetFramework.StandardAndCSharp); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", $$""" + { + // Code size 23 (0x17) + .maxstack 2 + .locals init (S V_0, //s + object V_1) //d + IL_0000: ldloca.s V_0 + IL_0002: initobj "S" + IL_0008: ldstr "Hello world" + IL_000d: stloc.1 + IL_000e: ldloca.s V_0 + IL_0010: ldloc.1 + IL_0011: call "void S.M({{argType}})" + IL_0016: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("string")] + [InlineData("int")] + public void RefStructReceiver03(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + try + { + s.M(d); + } + catch + { + System.Console.WriteLine("Caught exception"); + } + + ref struct S + { + public void M({{argType}} o) => System.Console.WriteLine(o); + } + """; + + var verifier = CompileAndVerify(code, expectedOutput: argType == "string" ? "Hello world" : "Caught exception", targetFramework: TargetFramework.StandardAndCSharp); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", $$""" + { + // Code size 101 (0x65) + .maxstack 4 + .locals init (S V_0, //s + object V_1) //d + IL_0000: ldloca.s V_0 + IL_0002: initobj "S" + IL_0008: ldstr "Hello world" + IL_000d: stloc.1 + .try + { + IL_000e: ldloca.s V_0 + IL_0010: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0015: brtrue.s IL_003b + IL_0017: ldc.i4.0 + IL_0018: ldtoken "{{argType}}" + IL_001d: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0022: ldtoken "Program" + IL_0027: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_002c: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" + IL_0031: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0036: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_003b: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0040: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" + IL_0045: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_004a: ldloc.1 + IL_004b: callvirt "{{argType}} System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" + IL_0050: call "void S.M({{argType}})" + IL_0055: leave.s IL_0064 + } + catch object + { + IL_0057: pop + IL_0058: ldstr "Caught exception" + IL_005d: call "void System.Console.WriteLine(string)" + IL_0062: leave.s IL_0064 + } + IL_0064: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("object")] + [InlineData("dynamic")] + public void RefStructReceiver04(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + s.M(d); + + ref struct S + { + public void M({{argType}} o) => System.Console.WriteLine(o); + public void M(string s) => System.Console.WriteLine(s); + } + """; + + CreateCompilation(code).VerifyDiagnostics( + // (4,1): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s.M(d); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(4, 1) + ); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("object")] + [InlineData("dynamic")] + public void RefStructReceiver05(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + s.M(d); + + ref struct S + { + public void M({{argType}} o) => System.Console.WriteLine(o); + public void M(T t) => System.Console.WriteLine(t); + } + """; + + CreateCompilation(code).VerifyDiagnostics( + // (4,1): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s.M(d); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(4, 1) + ); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("object")] + [InlineData("dynamic")] + public void RefStructReceiver06(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + _ = s[d]; + s[d] = 1; + + ref struct S + { + public int this[{{argType}} o] + { + get + { + System.Console.WriteLine(o); + return 0; + } + set => System.Console.WriteLine(o); + } + } + """; + + var verifier = CompileAndVerify(code, expectedOutput: """ + Hello world + Hello world + """, targetFramework: TargetFramework.StandardAndCSharp); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", $$""" + { + // Code size 33 (0x21) + .maxstack 3 + .locals init (S V_0, //s + object V_1) //d + IL_0000: ldloca.s V_0 + IL_0002: initobj "S" + IL_0008: ldstr "Hello world" + IL_000d: stloc.1 + IL_000e: ldloca.s V_0 + IL_0010: ldloc.1 + IL_0011: call "int S.this[{{argType}}].get" + IL_0016: pop + IL_0017: ldloca.s V_0 + IL_0019: ldloc.1 + IL_001a: ldc.i4.1 + IL_001b: call "void S.this[{{argType}}].set" + IL_0020: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("string")] + [InlineData("int")] + public void RefStructReceiver07(string argType) + { + var code = $$""" + dynamic d = "Hello world"; + + get(); + set(); + + void get() + { + var s = new S(); + try + { + _ = s[d]; + } + catch + { + System.Console.WriteLine("Caught exception"); + } + } + + void set() + { + var s = new S(); + try + { + s[d] = 1; + } + catch + { + System.Console.WriteLine("Caught exception"); + } + } + + ref struct S + { + public int this[{{argType}} o] + { + get + { + System.Console.WriteLine(o); + return 0; + } + set => System.Console.WriteLine(o); + } + } + """; + + var verifier = CompileAndVerify(code, expectedOutput: argType == "string" + ? """ + Hello world + Hello world + """ + : """ + Caught exception + Caught exception + """, targetFramework: TargetFramework.StandardAndCSharp); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.<
$>g__get|0_0(ref Program.<>c__DisplayClass0_0)", $$""" + { + // Code size 101 (0x65) + .maxstack 4 + .locals init (S V_0) //s + IL_0000: ldloca.s V_0 + IL_0002: initobj "S" + .try + { + IL_0008: ldloca.s V_0 + IL_000a: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_000f: brtrue.s IL_0035 + IL_0011: ldc.i4.0 + IL_0012: ldtoken "{{argType}}" + IL_0017: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_001c: ldtoken "Program" + IL_0021: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0026: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" + IL_002b: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0030: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0035: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_003a: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" + IL_003f: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0044: ldarg.0 + IL_0045: ldfld "dynamic Program.<>c__DisplayClass0_0.d" + IL_004a: callvirt "{{argType}} System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" + IL_004f: call "int S.this[{{argType}}].get" + IL_0054: pop + IL_0055: leave.s IL_0064 + } + catch object + { + IL_0057: pop + IL_0058: ldstr "Caught exception" + IL_005d: call "void System.Console.WriteLine(string)" + IL_0062: leave.s IL_0064 + } + IL_0064: ret + } + """); + + verifier.VerifyIL("Program.<
$>g__set|0_1(ref Program.<>c__DisplayClass0_0)", $$""" + { + // Code size 101 (0x65) + .maxstack 4 + .locals init (S V_0) //s + IL_0000: ldloca.s V_0 + IL_0002: initobj "S" + .try + { + IL_0008: ldloca.s V_0 + IL_000a: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" + IL_000f: brtrue.s IL_0035 + IL_0011: ldc.i4.0 + IL_0012: ldtoken "{{argType}}" + IL_0017: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_001c: ldtoken "Program" + IL_0021: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0026: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" + IL_002b: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0030: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" + IL_0035: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" + IL_003a: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" + IL_003f: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" + IL_0044: ldarg.0 + IL_0045: ldfld "dynamic Program.<>c__DisplayClass0_0.d" + IL_004a: callvirt "{{argType}} System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" + IL_004f: ldc.i4.1 + IL_0050: call "void S.this[{{argType}}].set" + IL_0055: leave.s IL_0064 + } + catch object + { + IL_0057: pop + IL_0058: ldstr "Caught exception" + IL_005d: call "void System.Console.WriteLine(string)" + IL_0062: leave.s IL_0064 + } + IL_0064: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("object")] + [InlineData("dynamic")] + public void RefStructReceiver08(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + _ = s[d]; + s[d] = 1; + + ref struct S + { + public int this[{{argType}} o] + { + get => 0; + set => System.Console.WriteLine(o); + } + + public int this[string s] + { + get => 0; + set => System.Console.WriteLine(s); + } + } + """; + + CreateCompilation(code).VerifyDiagnostics( + // (4,5): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // _ = s[d]; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(4, 5), + // (5,1): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s[d] = 1; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(5, 1) + ); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_01(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1.Test(""name"", value)); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + object Test(string name, object value); +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.Test(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object? Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_02(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1.Test(""name"", value)); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + object Test(string name, object value); +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.Test(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int? Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("dynamic I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1.Test(""name"", value)); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + dynamic Test(string name, object value); +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => i1.Test(""name"", value)").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic? Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_Extension(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C().Test(""name"", d); + System.Console.Write(result); + } +} + +static class Extensions +{ + public static int Test(this C c, string name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"new C().Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_01() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(name: ""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(name: ""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_02() + { + string source1 = @" +#pragma warning disable //CS8500: This takes the address of, gets the size of, or declares a pointer to a managed type ('string') + +unsafe public class C +{ + static void Main() + { + string name = ""name""; + dynamic d = 1; + var result = Test(&name, d); + System.Console.Write(result); + } + + static int Test(string* name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(&name, d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 C.Test(System.String* name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value, params System.Collections.Generic.List list) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Collections.Generic.List list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value, params int[] list) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_VoidReturning() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var a = Test1(d); + } + + static void Test1(int x) {} +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (7,13): error CS0815: Cannot assign void to an implicitly-typed variable + // var a = Test1(d); + Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "a = Test1(d)").WithArguments("void").WithLocation(7, 13) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + Test1(d)++; + var a = Test1(d); + System.Console.WriteLine(a); + } + + static int _test1 = 0; + static ref int Test1(int x) => ref _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_NoConversion() + { + string source = @" +unsafe public class C +{ + public static void Main() + { + int v = 0; + _test1 = &v; + + dynamic d = 1; + (*Test1(d))++; + var a = Test1(d); + System.Console.WriteLine(*a); + } + + static int* _test1; + static int* Test1(int x) => _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_LocalFunction([CombinatorialValues(0, 12, 13)] int version) + { + var parseOptions = version switch { 12 => TestOptions.Regular12, 13 => TestOptions.RegularNext, _ => TestOptions.RegularPreview }; + + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var e = test4(d); + System.Console.WriteLine(e); + + static int test4(int x) => x; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "e").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 e", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_Delegate(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source = @" +public class C +{ + static C M(Test i1, dynamic value) + { + var result = i1(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +delegate object Test(string name, object value); + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T); +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object Test.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Delegate_01() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(name: ""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value) => 123; + delegate int D(string name, object value); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(name: ""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Delegate_02() + { + string source1 = @" +#pragma warning disable //CS8500: This takes the address of, gets the size of, or declares a pointer to a managed type ('string') + +unsafe public class C +{ + static void Main() + { + string name = ""name""; + dynamic d = 1; + var result = Test(&name, d); + System.Console.Write(result); + } + + static D Test = (string* name, object value) => 123; + delegate int D(string* name, object value); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(&name, d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 C.D.Invoke(System.String* name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Delegate_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value, params System.Collections.Generic.List list) => 123; + delegate int D(string name, object value, params System.Collections.Generic.List list); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Collections.Generic.List list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Delegate() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value, params int[] list) => 123; + delegate int D(string name, object value, params int[] list); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_VoidReturning_Delegate() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var a = Test1(d); + } + + static D Test1 = null; +} + +delegate void D(int x); +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (7,13): error CS0815: Cannot assign void to an implicitly-typed variable + // var a = Test1(d); + Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "a = Test1(d)").WithArguments("void").WithLocation(7, 13) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Delegate() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + Test1(d)++; + var a = Test1(d); + System.Console.WriteLine(a); + } + + static int _test1 = 0; + static D Test1 = (int x) => ref _test1; + + delegate ref int D(int x); +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_NoConversion_Delegate() + { + string source = @" +unsafe public class C +{ + public static void Main() + { + int v = 0; + _test1 = &v; + + dynamic d = 1; + (*Test1(d))++; + var a = Test1(d); + System.Console.WriteLine(*a); + } + + static int* _test1; + static D Test1 = (int x) => _test1; + delegate int* D(int x); +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_Property_01(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Object I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1[""name"", value]); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + object this[string name, object value] {get;} +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.get_Item(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object? this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_Property_02(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1[""name"", value]); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + int this[string name, object value] {get;} +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.get_Item(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int? this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_03() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1[""name"", value]); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + dynamic this[string name, object value] {get;} +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => i1.get_Item(""name"", value)").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic? this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Property_01() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[name: ""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Property_02() + { + string source1 = @" +#pragma warning disable //CS8500: This takes the address of, gets the size of, or declares a pointer to a managed type ('string') + +unsafe public class C +{ + static void Main() + { + string name = ""name""; + dynamic d = 1; + var result = new C()[&name, d]; + System.Console.Write(result); + } + + int this[string* name, object value] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String* name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Property_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value, params System.Collections.Generic.List list] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Collections.Generic.List list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Property() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value, params int[] list] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Int32[] list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + c[d]++; + var a = c[d]; + System.Console.WriteLine(a); + } + + int _test1 = 0; + ref int this[int x] => ref _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + TypeInfo typeInfo; + + foreach (var elementAccess in tree.GetRoot().DescendantNodes().OfType()) + { + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + } + + var increment = tree.GetRoot().DescendantNodes().OfType().Single(); + typeInfo = model.GetTypeInfo(increment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_NoConversion_Property() + { + string source = @" +unsafe public class C +{ + public static void Main() + { + int v = 0; + var c = new C(); + c._test1 = &v; + + dynamic d = 1; + (*c[d])++; + var a = c[d]; + System.Console.WriteLine(*a); + } + + int* _test1; + int* this[int x] => _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + + TypeInfo typeInfo; + + foreach (var elementAccess in tree.GetRoot().DescendantNodes().OfType()) + { + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32* C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32*", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32*", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + } + + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = (int?)null; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = (int?)null; + Print(a); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic right = 2; + var a = c[d] = right; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic? right = (int?)null; + var a = c[d] = right; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (12,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(12, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + object o = null; + var c = new C(); + var a = c[d] = o; + System.Console.Write(a); + } + + System.IO.Stream this[int x] + { + get => null; + set {} + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.IO.Stream C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.IO.Stream", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.IO.Stream", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.IO.Stream", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.IO.Stream", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Object", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.IO.Stream", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (9,24): error CS0266: Cannot implicitly convert type 'object' to 'System.IO.Stream'. An explicit conversion exists (are you missing a cast?) + // var a = c[d] = o; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "o").WithArguments("object", "System.IO.Stream").WithLocation(9, 24) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = (int?)null; + Print(a); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Addition(System.Int32 left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += (int?)null; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (10,17): warning CS0458: The result of the expression is always 'null' of type 'dynamic' + // var a = c[d] += (int?)null; + Diagnostic(ErrorCode.WRN_AlwaysNull, "c[d] += (int?)null").WithArguments("dynamic").WithLocation(10, 17), + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72906")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Addition(dynamic left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += (int?)null; + Print(a); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullable analysis behavior is consistent with how dynamic operators are analyzed. + // See https://github.com/dotnet/roslyn/issues/72906, for example. + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72906")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += d; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Addition(System.Int32 left, dynamic right)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1 1").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + dynamic? right = null; + var c = new C(); + var a = c[d] += right; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullable analysis behavior is consistent with how dynamic operators are analyzed. + // See https://github.com/dotnet/roslyn/issues/72906, for example. + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_OperatorIsBoundStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + C2 right = new C2(); + var a = c[d] += right; + Print(a); + } + + C2 this[int x] + { + get => new C2(); + set {} + } + + static void Print(dynamic b) + { + } +} + +class C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + Assert.Null(symbolInfo.Symbol); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("C2", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + Assert.True(operation.Type.IsErrorType()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (9,17): error CS0019: Operator '+=' cannot be applied to operands of type 'C2' and 'C2' + // var a = c[d] += right; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "c[d] += right").WithArguments("+=", "C2", "C2").WithLocation(9, 17) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_CompoundAssignment() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Addition(System.Int32 left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += (int?)null; + Print(a); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (10,17): warning CS0458: The result of the expression is always 'null' of type 'int?' + // var a = c[d] += (int?)null; + Diagnostic(ErrorCode.WRN_AlwaysNull, "c[d] += (int?)null").WithArguments("int?").WithLocation(10, 17), + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PostfixIncrement_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "2 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + } + + int? _test1 = 2; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PostfixIncrement_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Increment(dynamic value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "2 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + } + + int? _test1 = 2; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PostfixIncrement_OperatorIsBoundStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + } + + + C2 this[int x] + { + get => new C2(); + set {} + } + + static void Print(dynamic b) + { + } +} + +class C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + Assert.Equal(CodeAnalysis.NullableFlowState.None, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + Assert.Equal(typeInfo.Type, typeInfo.ConvertedType); + symbolInfo = model.GetSymbolInfo(assignment); + Assert.Null(symbolInfo.Symbol); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + comp.VerifyDiagnostics( + // (8,17): error CS0023: Operator '++' cannot be applied to operand of type 'C2' + // var a = c[d]++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "c[d]++").WithArguments("++", "C2").WithLocation(8, 17) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PrefixIncrement_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "3 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + } + + int? _test1 = 2; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PrefixIncrement_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Increment(dynamic value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "3 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + } + + int? _test1 = 2; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_PrefixIncrement() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "3 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + } + + int? _test1 = 2; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= ""2""; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + string _test1 = null!; + string this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.String C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.String", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= (string?)null; + Print(a); + } + + string? _test1 = null; + string? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int? _test1 = null; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32? C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32?", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32?", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= (int?)null; + Print(a); + } + + int? _test1 = null; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= ""2""; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + string _test1 = null!; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= (string?)null; + Print(a); + } + + string? _test1 = null; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_04() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic right = ""2""; + var a = c[d] ??= right; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + string _test1 = null!; + string this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.String C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.String", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic? right = null; + var a = c[d] ??= right; + Print(a); + } + + string? _test1 = null; + string? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (12,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(12, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72912")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_RightSideIsConvertedStatically() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= ""2""; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + C2? _test1 = null; + C2? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} + +class C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("C2? a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2? C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2?", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("C2?", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + // The unexpected nullability warning is pre-existing condition - https://github.com/dotnet/roslyn/issues/72912 + comp.VerifyDiagnostics( + // (10,17): error CS0019: Operator '??=' cannot be applied to operands of type 'C2' and 'string' + // var a = c[d] ??= "2"; + Diagnostic(ErrorCode.ERR_BadBinaryOps, @"c[d] ??= ""2""").WithArguments("??=", "C2", "string").WithLocation(10, 17), + // (10,26): warning CS8619: Nullability of reference types in value of type 'string' doesn't match target type 'C2'. + // var a = c[d] ??= "2"; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, @"""2""").WithArguments("string", "C2").WithLocation(10, 26) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_Error() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= new C2(); + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + C2 _test1; + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} + +struct C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("C2", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (8,17): error CS0019: Operator '??=' cannot be applied to operands of type 'C2' and 'C2' + // var a = c[d] ??= new C2(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "c[d] ??= new C2()").WithArguments("??=", "C2", "C2").WithLocation(8, 17) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_ConditionalAssignment() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int? _test1 = null; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32? C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32?", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32?", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Print(expr); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Print(expr); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_03() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + dynamic v = 2; + var c = new C() { [d] = v }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Print(expr); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,70): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 70), + // (9,71): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 71), + // (9,76): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "v").WithLocation(9, 76) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = ""2"" }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (7,33): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var c = new C() { [d] = "2" }; + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""2""").WithArguments("string", "int").WithLocation(7, 33) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72916")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_Assignment() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_ObjectInitializer_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,67): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_ObjectInitializer_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,67): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_ObjectInitializer() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref C2 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,59): error CS8153: An expression tree lambda may not contain a call to a method, property, or indexer that returns by reference + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_RefReturningCallInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (7,35): error CS0117: 'C2' does not contain a definition for 'F' + // var c = new C() { [d] = { F = 2 } }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "F").WithArguments("C2", "F").WithLocation(7, 35) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_CollectionInitializer_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + System.Collections.Generic.List this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Collections.Generic.List C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + System.Collections.Generic.List this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,66): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_CollectionInitializer_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,66): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [ConditionalFact(typeof(NoIOperationValidation))] // IOperation validation is suppressed due to https://github.com/dotnet/roslyn/issues/72931 + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72931")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_CollectionInitializer() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + ref System.Collections.Generic.List this[int x] + { + get => ref _test1; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Collections.Generic.List C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Collections.Generic.List", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + ref System.Collections.Generic.List this[int x] + { + get => ref _test1; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,59): error CS8153: An expression tree lambda may not contain a call to a method, property, or indexer that returns by reference + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_RefReturningCallInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (7,33): error CS1922: Cannot initialize type 'C2' with a collection initializer because it does not implement 'System.Collections.IEnumerable' + // var c = new C() { [d] = {2} }; + Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{2}").WithArguments("C2").WithLocation(7, 33) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((dynamic)2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((dynamic?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (""2"", 123); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (8,30): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var a = (c[d], _) = ("2", 123); + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""2""").WithArguments("string", "int").WithLocation(8, 30) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment_Deconstruction_Tuple() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(System.Int32, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 (System.Int32, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a.Item1); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a.Item1").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72913")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsBoxing: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + // The meaningless warning is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72913 + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int' for parameter 'x' in 'void C2.Deconstruct(out int x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int", "x", "void C2.Deconstruct(out int x, out int y)").WithLocation(10, 18) + ); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + // The meaningless warning is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72913 + comp3.VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int?' for parameter 'x' in 'void C2.Deconstruct(out int? x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int?", "x", "void C2.Deconstruct(out int? x, out int y)").WithLocation(10, 18) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72914")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out dynamic x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + // The unexpected error is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72914 + comp.VerifyDiagnostics( + // (10,18): error CS0266: Cannot implicitly convert type 'dynamic' to 'int'. An explicit conversion exists (are you missing a cast?) + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c[d]").WithArguments("dynamic", "int").WithLocation(10, 18) + ); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out dynamic? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // The unexpected error is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72914 + comp3.VerifyDiagnostics( + // (10,18): error CS0266: Cannot implicitly convert type 'dynamic' to 'int?'. An explicit conversion exists (are you missing a cast?) + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c[d]").WithArguments("dynamic", "int?").WithLocation(10, 18) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public void Deconstruct(out string x, out int y) + { + (x, y) = (""2"", 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (8,18): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "c[d]").WithArguments("string", "int").WithLocation(8, 18) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment_Deconstruction_Method() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(System.Int32, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 (System.Int32, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a.Item1); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a.Item1").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Nested_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ((c[d], _), _) = ((2, 123), 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("((dynamic, System.Int32), System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + left = (TupleExpressionSyntax)left.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }, _] }); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + right = (TupleExpressionSyntax)right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "((2, 123), 124) 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Nested_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ((c[d], _), _) = (new C2(), 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("((dynamic, System.Int32), System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + left = (TupleExpressionSyntax)left.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }, _] }); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(C2, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(C2, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "((2, 123), 124) 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Conditional() + { + string source = @" +public class C +{ + public static void Main() + { + Test(true); + System.Console.Write("" ""); + Test(false); + } + + static void Test(bool b) + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = b ? (2, 123) : (3, 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2 (3, 124) 3").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_CSharp12_01() + { + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_CSharp12_02() + { + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_Extension_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C().Test(""name"", d); + System.Console.Write(result); + } +} + +static class Extensions +{ + public static int Test(this C c, string name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var model = comp1.GetSemanticModel(tree); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal(OperationKind.Invalid, model.GetOperation(call).Kind); + + comp1.VerifyDiagnostics( + // (7,22): error CS1973: 'C' has no applicable method named 'Test' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax. + // var result = new C().Test("name", d); + Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, @"new C().Test(""name"", d)").WithArguments("C", "Test").WithLocation(7, 22) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value, params int[] list) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Delegate_CSharp12() + { + string source = @" +public class C +{ + static C M(Test i1, dynamic value) + { + var result = i1(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +delegate object Test(string name, object value); + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T); +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object Test.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Delegate_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value, params int[] list) => 123; + delegate int D(string name, object value, params int[] list); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CSharp12_01() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Object I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CSharp12_02() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Property_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value, params int[] list] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Int32[] list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs index b1cf3f2f8a850..91fa4ec491db2 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Reflection.Metadata; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; @@ -21,20 +22,21 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics; public class InterceptorsTests : CSharpTestBase { - private static readonly (string, string) s_attributesSource = (""" + private static readonly (string text, string path) s_attributesSource = (""" namespace System.Runtime.CompilerServices; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public sealed class InterceptsLocationAttribute : Attribute { - public InterceptsLocationAttribute(string filePath, int line, int character) - { - } + public InterceptsLocationAttribute(string filePath, int line, int character) { } + public InterceptsLocationAttribute(int version, string data) { } } """, "attributes.cs"); private static readonly CSharpParseOptions RegularWithInterceptors = TestOptions.Regular.WithFeature("InterceptorsPreviewNamespaces", "global"); + private static readonly SyntaxTree s_attributesTree = CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors); + [Fact] public void FeatureFlag() { @@ -127,6 +129,69 @@ class D verifier.VerifyDiagnostics(); } + [Fact] + public void FeatureFlag_Granular_Checksum_01() + { + test(TestOptions.Regular.WithFeature("InterceptorsPreviewNamespaces", "NS"), expectedOutput: null, + // Interceptors.cs(7,10): error CS9137: The 'interceptors' experimental feature is not enabled in this namespace. Add '$(InterceptorsPreviewNamespaces);NS1' to your project. + // [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "eY+urAo7Kg2rsKgGSGjShwIAAABQcm9ncmFtLmNz")] + Diagnostic(ErrorCode.ERR_InterceptorsFeatureNotEnabled, "global::System.Runtime.CompilerServices.InterceptsLocationAttribute").WithArguments("$(InterceptorsPreviewNamespaces);NS1").WithLocation(7, 10)); + + test(TestOptions.Regular.WithFeature("InterceptorsPreviewNamespaces", "NS1.NS2"), expectedOutput: null, + // Interceptors.cs(7,10): error CS9137: The 'interceptors' experimental feature is not enabled in this namespace. Add '$(InterceptorsPreviewNamespaces);NS1' to your project. + // [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "eY+urAo7Kg2rsKgGSGjShwIAAABQcm9ncmFtLmNz")] + Diagnostic(ErrorCode.ERR_InterceptorsFeatureNotEnabled, "global::System.Runtime.CompilerServices.InterceptsLocationAttribute").WithArguments("$(InterceptorsPreviewNamespaces);NS1").WithLocation(7, 10)); + + test(TestOptions.Regular.WithFeature("InterceptorsPreviewNamespaces", "NS1"), expectedOutput: "1"); + + test(TestOptions.Regular.WithFeature("InterceptorsPreviewNamespaces", "NS1;NS2"), expectedOutput: "1"); + + void test(CSharpParseOptions options, string? expectedOutput, params DiagnosticDescription[] expected) + { + var source = CSharpTestSource.Parse(""" + C.M(); + + class C + { + public static void M() => throw null!; + } + """, path: "Program.cs", options); + + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + + var model = comp.GetSemanticModel(source); + var invocation = source.GetRoot().DescendantNodes().OfType().Single(); + var interceptableLocation = model.GetInterceptableLocation(invocation)!; + + var interceptors = CSharpTestSource.Parse($$""" + using System; + + namespace NS1 + { + class D + { + {{interceptableLocation.GetInterceptsLocationAttributeSyntax()}} + public static void M() => Console.Write(1); + } + } + """, path: "Interceptors.cs", options); + var attributesTree = CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, options: options); + + comp = CreateCompilation([source, interceptors, attributesTree]); + + if (expectedOutput == null) + { + comp.VerifyEmitDiagnostics(expected); + } + else + { + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(expected); + } + } + } + [Fact] public void FeatureFlag_Granular_02() { @@ -1790,6 +1855,62 @@ public static string Prop ); } + [Fact] + public void InterceptsLocation_BadMethodKind_Checksum() + { + var source = CSharpTestSource.Parse(""" + class Program + { + public static void InterceptableMethod(string param) { } + + public static void Main() + { + InterceptableMethod(""); + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source); + var invocation = source.GetRoot().DescendantNodes().OfType().Single(); + var model = comp.GetSemanticModel(source); + var location = model.GetInterceptableLocation(invocation)!; + + var interceptors = CSharpTestSource.Parse($$""" + using System.Runtime.CompilerServices; + + class C + { + static void M() + { + Interceptor1(""); + var lambda = [InterceptsLocation({{location.Version}}, "{{location.Data}}")] (string param) => { }; // 1 + + [InterceptsLocation({{location.Version}}, "{{location.Data}}")] // 2 + static void Interceptor1(string param) { } + } + + public static string Prop + { + [InterceptsLocation({{location.Version}}, "{{location.Data}}")] // 3 + set { } + } + } + """, "Interceptors.cs", RegularWithInterceptors); + + comp = CreateCompilation([source, interceptors, s_attributesTree]); + comp.VerifyDiagnostics( + // Interceptors.cs(8,23): error CS9146: An interceptor method must be an ordinary member method. + // var lambda = [InterceptsLocation(1, "OjpNlan67EMibFykRLWBLXgAAABQcm9ncmFtLmNz")] (string param) => { }; // 1 + Diagnostic(ErrorCode.ERR_InterceptorMethodMustBeOrdinary, "InterceptsLocation").WithLocation(8, 23), + // Interceptors.cs(10,10): error CS9146: An interceptor method must be an ordinary member method. + // [InterceptsLocation(1, "OjpNlan67EMibFykRLWBLXgAAABQcm9ncmFtLmNz")] // 2 + Diagnostic(ErrorCode.ERR_InterceptorMethodMustBeOrdinary, "InterceptsLocation").WithLocation(10, 10), + // Interceptors.cs(16,10): error CS9146: An interceptor method must be an ordinary member method. + // [InterceptsLocation(1, "OjpNlan67EMibFykRLWBLXgAAABQcm9ncmFtLmNz")] // 3 + Diagnostic(ErrorCode.ERR_InterceptorMethodMustBeOrdinary, "InterceptsLocation").WithLocation(16, 10) + ); + } + [Fact] public void InterceptableMethod_BadMethodKind_01() { @@ -1829,6 +1950,64 @@ static void Interceptor1() { } ); } + [Fact] + public void InterceptableMethod_BadMethodKind_Checksum_01() + { + var source = CSharpTestSource.Parse(""" + class Program + { + public static void Main() + { + // property + _ = Prop; // 1 ('Prop') + + // constructor + new Program(); // 2 ('new'), 3 ('Program') + } + + public static int Prop { get; } + } + """, "Program.cs", options: RegularWithInterceptors); + + var comp = CreateCompilation(source); + var model = (CSharpSemanticModel)comp.GetSemanticModel(source); + var root = source.GetRoot(); + + var node1 = root.DescendantNodes().First(node => node is IdentifierNameSyntax name && name.Identifier.Text == "Prop"); + var location1 = model.GetInterceptableLocationInternal(node1, cancellationToken: default); + + var node2 = root.DescendantNodes().Single(node => node is ObjectCreationExpressionSyntax); + var location2 = model.GetInterceptableLocationInternal(node2, cancellationToken: default); + + var node3 = root.DescendantNodes().Last(node => node is IdentifierNameSyntax name && name.Identifier.Text == "Program"); + var location3 = model.GetInterceptableLocationInternal(node3, cancellationToken: default); + + var interceptors = CSharpTestSource.Parse($$""" + using System.Runtime.CompilerServices; + + class C + { + [InterceptsLocation({{location1.Version}}, "{{location1.Data}}")] // 1 + [InterceptsLocation({{location2.Version}}, "{{location2.Data}}")] // 2 + [InterceptsLocation({{location3.Version}}, "{{location3.Data}}")] // 3 + static void Interceptor1() { } + } + """, "Interceptors.cs", RegularWithInterceptors); + + comp = CreateCompilation([source, interceptors, s_attributesTree]); + comp.VerifyDiagnostics( + // Interceptors.cs(5,6): error CS9151: Possible method name 'Prop' cannot be intercepted because it is not being invoked. + // [InterceptsLocation(1, "hD44wQkJk1har7RM7oznpFkAAABQcm9ncmFtLmNz")] // 1 + Diagnostic(ErrorCode.ERR_InterceptorNameNotInvoked, "InterceptsLocation").WithArguments("Prop").WithLocation(5, 6), + // Interceptors.cs(6,6): error CS9141: The provided line and character number does not refer to an interceptable method name, but rather to token 'new'. + // [InterceptsLocation(1, "hD44wQkJk1har7RM7oznpG4AAABQcm9ncmFtLmNz")] // 2 + Diagnostic(ErrorCode.ERR_InterceptorPositionBadToken, "InterceptsLocation").WithArguments("new").WithLocation(6, 6), + // Interceptors.cs(7,6): error CS9151: Possible method name 'Program' cannot be intercepted because it is not being invoked. + // [InterceptsLocation(1, "hD44wQkJk1har7RM7oznpJQAAABQcm9ncmFtLmNz")] // 3 + Diagnostic(ErrorCode.ERR_InterceptorNameNotInvoked, "InterceptsLocation").WithArguments("Program").WithLocation(7, 6) + ); + } + [Fact] public void InterceptableMethod_BadMethodKind_02() { @@ -1946,6 +2125,51 @@ static class D Diagnostic(ErrorCode.ERR_InterceptorContainingTypeCannotBeGeneric, @"InterceptsLocation(""Program.cs"", 15, 11)").WithArguments("D.Interceptor1(string)").WithLocation(21, 6)); } + [Fact] + public void InterceptorCannotBeGeneric_Checksum_02() + { + var source = CSharpTestSource.Parse(""" + using System; + + interface I1 { } + class C : I1 + { + + public static void InterceptableMethod(string param) { Console.Write("interceptable " + param); } + } + + static class Program + { + public static void Main() + { + C.InterceptableMethod("call site"); + } + } + """, "Program.cs", options: RegularWithInterceptors); + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var invocation = source.GetRoot().DescendantNodes().OfType().Last(); + var model = comp.GetSemanticModel(source); + var location = model.GetInterceptableLocation(invocation)!; + + var interceptors = CSharpTestSource.Parse($$""" + using System; + + static class D + { + {{location.GetInterceptsLocationAttributeSyntax()}} + public static void Interceptor1(string param) { Console.Write("interceptor " + param); } + } + """, "Interceptors.cs", options: RegularWithInterceptors); + + comp = CreateCompilation([source, interceptors, s_attributesTree]); + comp.VerifyEmitDiagnostics( + // Interceptors.cs(5,6): error CS9138: Method 'D.Interceptor1(string)' cannot be used as an interceptor because its containing type has type parameters. + // [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "ZCdvmiprtZ938pueLU5g6OsAAABQcm9ncmFtLmNz")] + Diagnostic(ErrorCode.ERR_InterceptorContainingTypeCannotBeGeneric, "global::System.Runtime.CompilerServices.InterceptsLocationAttribute").WithArguments("D.Interceptor1(string)").WithLocation(5, 6)); + } + [Fact] public void InterceptorCannotBeGeneric_03() { @@ -2547,9 +2771,9 @@ static class D // Program.cs(8,39): error CS0103: The name 'ERROR' does not exist in the current context // [InterceptsLocation("Program.cs", ERROR, 1)] Diagnostic(ErrorCode.ERR_NameNotInContext, "ERROR").WithArguments("ERROR").WithLocation(8, 39), - // Program.cs(9,6): error CS7036: There is no argument given that corresponds to the required parameter 'filePath' of 'InterceptsLocationAttribute.InterceptsLocationAttribute(string, int, int)' + // Program.cs(9,6): error CS1729: 'InterceptsLocationAttribute' does not contain a constructor that takes 0 arguments // [InterceptsLocation()] - Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "InterceptsLocation()").WithArguments("filePath", "System.Runtime.CompilerServices.InterceptsLocationAttribute.InterceptsLocationAttribute(string, int, int)").WithLocation(9, 6) + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "InterceptsLocation()").WithArguments("System.Runtime.CompilerServices.InterceptsLocationAttribute", "0").WithLocation(9, 6) ); } @@ -3026,6 +3250,108 @@ public static void Main() ); } + [Fact] + public void InterceptsLocationBadPosition_Checksum_01() + { + var sourceTree = CSharpTestSource.Parse(""" + using System.Runtime.CompilerServices; + using System; + + interface I1 { } + class C : I1 { } + + static class Program + { + + public static I1 InterceptableMethod(this I1 i1, string param) { Console.Write("interceptable " + param); return i1; } + + public static void Main() + { + var c = new C(); + c.InterceptableMethod("call site"); + } + } + """, options: RegularWithInterceptors); + + // test unexpected position within interceptable name token + var interceptableName = sourceTree.GetRoot().DescendantNodes().OfType().Last().GetInterceptableNameSyntax()!; + var position = interceptableName.Position + 1; + + var builder = new BlobBuilder(); + builder.WriteBytes(sourceTree.GetText().GetContentHash()); + builder.WriteInt32(position); + builder.WriteUTF8("Error"); + + var base64 = Convert.ToBase64String(builder.ToArray()); + + var interceptorTree = CSharpTestSource.Parse($$""" + using System.Runtime.CompilerServices; + using System; + + static class D + { + [InterceptsLocation(1, "{{base64}}")] + public static I1 Interceptor1(this I1 i1, string param) { Console.Write("interceptor " + param); return i1; } + } + """, options: RegularWithInterceptors); + var comp = CreateCompilation([sourceTree, interceptorTree, s_attributesTree]); + comp.VerifyEmitDiagnostics( + // (6,6): error CS9235: The data argument to InterceptsLocationAttribute refers to an invalid position in file 'Error'. + // [InterceptsLocation(1, "ExWKMussA+NMlN5J0QNXiEMBAABFcnJvcg==")] + Diagnostic(ErrorCode.ERR_InterceptsLocationDataInvalidPosition, "InterceptsLocation").WithArguments("Error").WithLocation(6, 6) + ); + } + + [Theory] + [InlineData(-1)] // test invalid position + [InlineData(99999)] // test position past end of the file + public void InterceptsLocationBadPosition_Checksum_02(int position) + { + var sourceTree = CSharpTestSource.Parse(""" + using System.Runtime.CompilerServices; + using System; + + interface I1 { } + class C : I1 { } + + static class Program + { + + public static I1 InterceptableMethod(this I1 i1, string param) { Console.Write("interceptable " + param); return i1; } + + public static void Main() + { + var c = new C(); + c.InterceptableMethod("call site"); + } + } + """, options: RegularWithInterceptors); + + var builder = new BlobBuilder(); + builder.WriteBytes(sourceTree.GetText().GetContentHash()); + builder.WriteInt32(position); + builder.WriteUTF8("Error"); + + var base64 = Convert.ToBase64String(builder.ToArray()); + + var interceptorTree = CSharpTestSource.Parse($$""" + using System.Runtime.CompilerServices; + using System; + + static class D + { + [InterceptsLocation(1, "{{base64}}")] + public static I1 Interceptor1(this I1 i1, string param) { Console.Write("interceptor " + param); return i1; } + } + """, options: RegularWithInterceptors); + var comp = CreateCompilation([sourceTree, interceptorTree, s_attributesTree]); + comp.VerifyEmitDiagnostics( + // (6,6): error CS9235: The data argument to InterceptsLocationAttribute refers to an invalid position in file 'Error'. + // [InterceptsLocation(1, "ExWKMussA+NMlN5J0QNXiJ+GAQBFcnJvcg==")] + Diagnostic(ErrorCode.ERR_InterceptsLocationDataInvalidPosition, "InterceptsLocation").WithArguments("Error").WithLocation(6, 6) + ); + } + [Fact] public void SignatureMismatch_01() { @@ -5328,6 +5654,45 @@ public static void Interceptor() { } Diagnostic(ErrorCode.ERR_InterceptorCannotUseUnmanagedCallersOnly, @"InterceptsLocation(""Program.cs"", 5, 3)").WithLocation(14, 6)); } + [Fact] + public void InterceptorUnmanagedCallersOnly_Checksum() + { + var source = CSharpTestSource.Parse(""" + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System; + + C.Interceptable(); + + class C + { + public static void Interceptable() { } + } + """, "Program.cs", RegularWithInterceptors); + var comp = CreateCompilation(source); + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + static class D + { + [InterceptsLocation({{locationSpecifier.Version}}, "{{locationSpecifier.Data}}")] + [UnmanagedCallersOnly] + public static void Interceptor() { } + } + """, "Interceptors.cs", RegularWithInterceptors); + + comp = CreateCompilation([source, interceptors, s_attributesTree, CSharpTestSource.Parse(UnmanagedCallersOnlyAttributeDefinition, "UnmanagedCallersOnlyAttribute.cs", RegularWithInterceptors)]); + comp.VerifyEmitDiagnostics( + // Interceptors.cs(6,6): error CS9161: An interceptor cannot be marked with 'UnmanagedCallersOnlyAttribute'. + // [InterceptsLocation(1, "SnNcyOJQR8oIDrJpnwBmCWIAAABQcm9ncmFtLmNz")] + Diagnostic(ErrorCode.ERR_InterceptorCannotUseUnmanagedCallersOnly, "InterceptsLocation").WithLocation(6, 6)); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70841")] public void InterceptorEnumBaseMethod() { @@ -6422,4 +6787,730 @@ public static class D Assert.Null(model.GetInterceptorMethod(call)); } + + // https://github.com/dotnet/roslyn/issues/72265 + // As part of the work to drop support for file path based interceptors, a significant number of existing tests here will need to be ported to checksum-based. + + [Fact] + public void Checksum_01() + { + var source = CSharpTestSource.Parse(""" + class C + { + static void M() => throw null!; + + static void Main() + { + M(); + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source); + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + using System; + + static class Interceptors + { + {{locationSpecifier.GetInterceptsLocationAttributeSyntax()}} + public static void M1() => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var verifier = CompileAndVerify([source, interceptors, CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors)], expectedOutput: "1"); + verifier.VerifyDiagnostics(); + + // again, but using the accessors for specifically retrieving the individual attribute arguments + interceptors = CSharpTestSource.Parse($$""" + using System; + using System.Runtime.CompilerServices; + + static class Interceptors + { + [InterceptsLocation({{locationSpecifier!.Version}}, "{{locationSpecifier.Data}}")] + public static void M1() => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + verifier = CompileAndVerify([source, interceptors, CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors)], expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void Checksum_02() + { + var tree = CSharpTestSource.Parse(""" + class C + { + static void M() => throw null!; + + static void Main() + { + M(); + M(); + } + } + """.NormalizeLineEndings(), "path/to/Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(tree); + var model = comp.GetSemanticModel(tree); + if (tree.GetRoot().DescendantNodes().OfType().ToList() is not [var node, var otherNode]) + { + throw ExceptionUtilities.Unreachable(); + } + + var locationSpecifier = model.GetInterceptableLocation(node); + Assert.False(locationSpecifier!.Equals(null)); + + // Verify behaviors of the public APIs. + Assert.Equal("path/to/Program.cs(7,9)", locationSpecifier!.GetDisplayLocation()); + Assert.Equal(1, locationSpecifier.Version); + Assert.Equal(locationSpecifier, locationSpecifier); + + Assert.NotSame(locationSpecifier, model.GetInterceptableLocation(node)); + Assert.Equal(locationSpecifier, model.GetInterceptableLocation(node)); + Assert.Equal(locationSpecifier.GetHashCode(), model.GetInterceptableLocation(node)!.GetHashCode()); + + // If Data changes it might be the case that 'SourceText.GetContentHash()' has changed algorithms. + // In this case we need to adjust the SourceMethodSymbolWithAttributes.DecodeInterceptsLocationAttribute impl to remain compatible with v1 and consider introducing a v2 which uses the new content hash algorithm. + AssertEx.Equal("xRCCFCvTOZMORzSr/fZQFlIAAABQcm9ncmFtLmNz", locationSpecifier.Data); + AssertEx.Equal("""[global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "xRCCFCvTOZMORzSr/fZQFlIAAABQcm9ncmFtLmNz")]""", locationSpecifier.GetInterceptsLocationAttributeSyntax()); + + var otherLocation = model.GetInterceptableLocation(otherNode)!; + Assert.NotEqual(locationSpecifier, otherLocation); + // While it is not incorrect for the HashCodes of these instances to be equal, we don't expect it in this case. + Assert.NotEqual(locationSpecifier.GetHashCode(), otherLocation.GetHashCode()); + + Assert.Equal("path/to/Program.cs(8,9)", otherLocation.GetDisplayLocation()); + AssertEx.Equal("xRCCFCvTOZMORzSr/fZQFmAAAABQcm9ncmFtLmNz", otherLocation.Data); + AssertEx.Equal("""[global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "xRCCFCvTOZMORzSr/fZQFmAAAABQcm9ncmFtLmNz")]""", otherLocation.GetInterceptsLocationAttributeSyntax()); + + } + + [Fact] + public void Checksum_03() + { + // Invalid base64 + var interceptors = CSharpTestSource.Parse($$""" + using System; + using System.Runtime.CompilerServices; + + static class Interceptors + { + [InterceptsLocation(1, "jB4qgCy292LkEGCwmD+R6AcAAAAJAAAAUHJvZ3JhbS5jcw===")] + public static void M1() => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var comp = CreateCompilation([interceptors, CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors)]); + comp.VerifyEmitDiagnostics( + // Interceptors.cs(6,6): error CS9231: The data argument to InterceptsLocationAttribute is not in the correct format. + // [InterceptsLocation(1, "jB4qgCy292LkEGCwmD+R6AcAAAAJAAAAUHJvZ3JhbS5jcw===")] + Diagnostic(ErrorCode.ERR_InterceptsLocationDataInvalidFormat, "InterceptsLocation").WithLocation(6, 6)); + } + + [Fact] + public void Checksum_04() + { + // Test invalid UTF-8 encoded to base64 + + var builder = new BlobBuilder(); + // all zeros checksum and zero position + builder.WriteBytes(value: 0, byteCount: 20); + + // write invalid utf-8 + builder.WriteByte(0xc0); + + var base64 = Convert.ToBase64String(builder.ToArray()); + + var interceptors = CSharpTestSource.Parse($$""" + using System; + using System.Runtime.CompilerServices; + + static class Interceptors + { + [InterceptsLocation(1, "{{base64}}")] + public static void M1() => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var comp = CreateCompilation([interceptors, CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors)]); + comp.VerifyEmitDiagnostics( + // Interceptors.cs(6,6): error CS9231: The data argument to InterceptsLocationAttribute is not in the correct format. + // [InterceptsLocation(1, "AAAAAAAAAAAAAAAAAAAAAAAAAADA")] + Diagnostic(ErrorCode.ERR_InterceptsLocationDataInvalidFormat, "InterceptsLocation").WithLocation(6, 6)); + } + + [Theory] + [InlineData("")] + [InlineData("AA==")] + public void Checksum_05(string data) + { + // Test data value too small + var interceptors = CSharpTestSource.Parse($$""" + using System; + using System.Runtime.CompilerServices; + + static class Interceptors + { + [InterceptsLocation(1, "{{data}}")] + public static void M1() => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var comp = CreateCompilation([interceptors, CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors)]); + comp.VerifyEmitDiagnostics( + // Interceptors.cs(6,6): error CS9231: The data argument to InterceptsLocationAttribute is not in the correct format. + // [InterceptsLocation(1, "")] + Diagnostic(ErrorCode.ERR_InterceptsLocationDataInvalidFormat, "InterceptsLocation").WithLocation(6, 6)); + } + + [Fact] + public void Checksum_06() + { + // Null data + var interceptors = CSharpTestSource.Parse($$""" + using System; + using System.Runtime.CompilerServices; + + static class Interceptors + { + [InterceptsLocation(1, null)] + public static void M1() => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var comp = CreateCompilation([interceptors, CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors)]); + comp.VerifyEmitDiagnostics( + // Interceptors.cs(6,6): error CS9231: The data argument to InterceptsLocationAttribute is not in the correct format. + // [InterceptsLocation(1, null)] + Diagnostic(ErrorCode.ERR_InterceptsLocationDataInvalidFormat, "InterceptsLocation").WithLocation(6, 6)); + } + + [Fact] + public void Checksum_07() + { + // File not found + + var source = CSharpTestSource.Parse(""" + class C + { + static void M() => throw null!; + + static void Main() + { + M(); + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source); + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + using System; + using System.Runtime.CompilerServices; + + static class Interceptors + { + [InterceptsLocation({{locationSpecifier.Version}}, "{{locationSpecifier.Data}}")] + public static void M1() => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var comp1 = CreateCompilation([interceptors, CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors)]); + comp1.VerifyEmitDiagnostics( + // Interceptors.cs(6,6): error CS9234: Cannot intercept a call in file 'Program.cs' because a matching file was not found in the compilation. + // [InterceptsLocation(1, "jB4qgCy292LkEGCwmD+R6FIAAABQcm9ncmFtLmNz")] + Diagnostic(ErrorCode.ERR_InterceptsLocationFileNotFound, "InterceptsLocation").WithArguments("Program.cs").WithLocation(6, 6)); + } + + [Fact] + public void Checksum_08() + { + // Duplicate file + + var source = """ + class C + { + static void M() => throw null!; + + static void Main() + { + M(); + } + } + """; + var sourceTree1 = CSharpTestSource.Parse(source, path: "Program1.cs", options: RegularWithInterceptors); + + var comp = CreateCompilation(sourceTree1); + var model = comp.GetSemanticModel(sourceTree1); + var node = sourceTree1.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + using System; + using System.Runtime.CompilerServices; + + static class Interceptors + { + [InterceptsLocation({{locationSpecifier.Version}}, "{{locationSpecifier.Data}}")] + public static void M1() => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var comp1 = CreateCompilation([ + sourceTree1, + CSharpTestSource.Parse(source, path: "Program2.cs", options: RegularWithInterceptors), + interceptors, + CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors)]); + comp1.GetDiagnostics().Where(d => d.Location.SourceTree == interceptors).Verify( + // Interceptors.cs(6,6): error CS9233: Cannot intercept a call in file 'Program1.cs' because it is duplicated elsewhere in the compilation. + // [InterceptsLocation(1, "jB4qgCy292LkEGCwmD+R6FIAAABQcm9ncmFtMS5jcw==")] + Diagnostic(ErrorCode.ERR_InterceptsLocationDuplicateFile, "InterceptsLocation").WithArguments("Program1.cs").WithLocation(6, 6)); + } + + [Fact] + public void Checksum_09() + { + // Call can be intercepted syntactically but a semantic error occurs when actually performing it. + + var source = CSharpTestSource.Parse(""" + using System; + + class C + { + static Action P { get; } = null!; + + static void Main() + { + P(); + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source); + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + using System; + + static class Interceptors + { + {{locationSpecifier.GetInterceptsLocationAttributeSyntax()}} + public static void P1(this C c) => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + comp = CreateCompilation([source, interceptors, CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors)]); + comp.VerifyEmitDiagnostics( + // Interceptors.cs(5,6): error CS9207: Cannot intercept 'P' because it is not an invocation of an ordinary member method. + // [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "ZnP1PXDK5WDD07FTErR9eWUAAABQcm9ncmFtLmNz")] + Diagnostic(ErrorCode.ERR_InterceptableMethodMustBeOrdinary, "global::System.Runtime.CompilerServices.InterceptsLocationAttribute").WithArguments("P").WithLocation(5, 6)); + } + + [Fact] + public void Checksum_10() + { + // Call cannot be intercepted syntactically + + var source = CSharpTestSource.Parse(""" + using System; + + static class C + { + public static void M(this object obj) => throw null!; + + static void Main() + { + null(); + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // Program.cs(9,9): error CS0149: Method name expected + // null(); + Diagnostic(ErrorCode.ERR_MethodNameExpected, "null").WithLocation(9, 9)); + + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocation(node); + Assert.Null(locationSpecifier); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(2)] + [InlineData(9999)] + public void Checksum_11(int version) + { + // Bad version + var interceptors = CSharpTestSource.Parse($$""" + using System; + using System.Runtime.CompilerServices; + + static class Interceptors + { + [InterceptsLocation({{version}}, "jB4qgCy292LkEGCwmD+R6AcAAAAJAAAAUHJvZ3JhbS5jcw===")] + public static void M1() => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var comp = CreateCompilation([interceptors, CSharpTestSource.Parse(s_attributesSource.text, s_attributesSource.path, RegularWithInterceptors)]); + comp.VerifyEmitDiagnostics( + // Interceptors.cs(6,6): error CS9232: Version '0' of the interceptors format is not supported. The latest supported version is '1'. + // [InterceptsLocation(0, "jB4qgCy292LkEGCwmD+R6AcAAAAJAAAAUHJvZ3JhbS5jcw===")] + Diagnostic(ErrorCode.ERR_InterceptsLocationUnsupportedVersion, "InterceptsLocation").WithArguments($"{version}").WithLocation(6, 6)); + } + + [Fact] + public void Checksum_12() + { + // Attempt to insert null paths into InterceptableLocation. + + var tree = CSharpTestSource.Parse(""" + class C + { + static void M() => throw null!; + + static void Main() + { + M(); + } + } + """.NormalizeLineEndings(), path: null, RegularWithInterceptors); + Assert.Equal("", tree.FilePath); + + var comp = CreateCompilation(tree); + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + Assert.Equal("(7,9)", locationSpecifier.GetDisplayLocation()); + AssertEx.Equal("""[global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "jB4qgCy292LkEGCwmD+R6FIAAAA=")]""", locationSpecifier.GetInterceptsLocationAttributeSyntax()); + } + + [Fact] + public void ConditionalAccess_ReferenceType_01() + { + // Conditional access on a non-null value + var source = CSharpTestSource.Parse(""" + class C + { + void M() => throw null!; + + static void Main() + { + var c = new C(); + c?.M(); + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source); + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + #nullable enable + using System; + + static class Interceptors + { + {{locationSpecifier.GetInterceptsLocationAttributeSyntax()}} + public static void M1(this C c) => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var verifier = CompileAndVerify([source, interceptors, s_attributesTree], expectedOutput: "1"); + verifier.VerifyDiagnostics(); + + comp = (CSharpCompilation)verifier.Compilation; + model = comp.GetSemanticModel(source); + var method = model.GetInterceptorMethod(node); + Assert.Equal("void Interceptors.M1(this C c)", method.ToTestDisplayString()); + } + + [Fact] + public void ConditionalAccess_ReferenceType_02() + { + // Conditional access on a null value + var source = CSharpTestSource.Parse(""" + #nullable enable + using System; + + class C + { + void M() => throw null!; + + static void Main() + { + C? c = null; + c?.M(); + Console.Write(1); + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source); + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().First(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + #nullable enable + + static class Interceptors + { + {{locationSpecifier.GetInterceptsLocationAttributeSyntax()}} + public static void M1(this C c) => throw null!; + } + """, "Interceptors.cs", RegularWithInterceptors); + + var verifier = CompileAndVerify([source, interceptors, s_attributesTree], expectedOutput: "1"); + verifier.VerifyDiagnostics(); + + comp = (CSharpCompilation)verifier.Compilation; + model = comp.GetSemanticModel(source); + var method = model.GetInterceptorMethod(node); + Assert.Equal("void Interceptors.M1(this C c)", method.ToTestDisplayString()); + } + + [Fact] + public void ConditionalAccess_NotAnInvocation() + { + // use a location specifier which refers to a conditional access that is not being invoked. + var source = CSharpTestSource.Parse(""" + class C + { + int P => throw null!; + + static void Main() + { + var c = new C(); + _ = c?.P; + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source); + var model = (CSharpSemanticModel)comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocationInternal(node.Name, cancellationToken: default)!; + + var interceptors = CSharpTestSource.Parse($$""" + #nullable enable + using System; + + static class Interceptors + { + {{locationSpecifier.GetInterceptsLocationAttributeSyntax()}} + public static void M1(this C c) => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + comp = CreateCompilation([source, interceptors, s_attributesTree]); + comp.VerifyEmitDiagnostics( + // Interceptors.cs(6,6): error CS9151: Possible method name 'P' cannot be intercepted because it is not being invoked. + // [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "q2jDXUSFcU71GJHh7313cHEAAABQcm9ncmFtLmNz")] + Diagnostic(ErrorCode.ERR_InterceptorNameNotInvoked, "global::System.Runtime.CompilerServices.InterceptsLocationAttribute").WithArguments("P").WithLocation(6, 6)); + } + + [Fact] + public void ConditionalAccess_ValueType_01() + { + // Conditional access on a nullable value type with a non-null value + // Note that we can't intercept a conditional-access with an extension due to https://github.com/dotnet/roslyn/issues/71657 + var source = CSharpTestSource.Parse(""" + partial struct S + { + void M() => throw null!; + + static void Main() + { + S? s = new S(); + s?.M(); + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source); + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().First(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + using System; + + partial struct S + { + {{locationSpecifier.GetInterceptsLocationAttributeSyntax()}} + public void M1() => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var verifier = CompileAndVerify([source, interceptors, s_attributesTree], expectedOutput: "1"); + verifier.VerifyDiagnostics(); + + comp = (CSharpCompilation)verifier.Compilation; + model = comp.GetSemanticModel(source); + var method = model.GetInterceptorMethod(node); + Assert.Equal("void S.M1()", method.ToTestDisplayString()); + } + + [Fact] + public void ConditionalAccess_ValueType_02() + { + // Conditional access on a nullable value type with a null value + var source = CSharpTestSource.Parse(""" + using System; + + partial struct S + { + void M() => throw null!; + + static void Main() + { + S? s = null; + s?.M(); + Console.Write(1); + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source); + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().First(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + partial struct S + { + {{locationSpecifier.GetInterceptsLocationAttributeSyntax()}} + public void M1() => throw null!; + } + """, "Interceptors.cs", RegularWithInterceptors); + + var verifier = CompileAndVerify([source, interceptors, s_attributesTree], expectedOutput: "1"); + verifier.VerifyDiagnostics(); + + comp = (CSharpCompilation)verifier.Compilation; + model = comp.GetSemanticModel(source); + var method = model.GetInterceptorMethod(node); + Assert.Equal("void S.M1()", method.ToTestDisplayString()); + } + + [Theory] + [InlineData("p->M();")] + [InlineData("(*p).M();")] + public void PointerAccess_01(string invocation) + { + var source = CSharpTestSource.Parse($$""" + struct S + { + void M() => throw null!; + + static unsafe void Main() + { + S s = default; + S* p = &s; + {{invocation}} + } + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugExe); + CompileAndVerify(comp, verify: Verification.Fails); + + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + #nullable enable + using System; + + static class Interceptors + { + {{locationSpecifier.GetInterceptsLocationAttributeSyntax()}} + public static void M1(this ref S s) => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var verifier = CompileAndVerify( + [source, interceptors, s_attributesTree], + options: TestOptions.UnsafeDebugExe, + verify: Verification.Fails, + expectedOutput: "1"); + verifier.VerifyDiagnostics(); + + comp = (CSharpCompilation)verifier.Compilation; + model = comp.GetSemanticModel(source); + var method = model.GetInterceptorMethod(node); + Assert.Equal("void Interceptors.M1(this ref S s)", method.ToTestDisplayString()); + } + + [Theory] + [CombinatorialData] + public void PointerAccess_02([CombinatorialValues("p->M();", "(*p).M();")] string invocation, [CombinatorialValues("", "ref ")] string refKind) + { + // Original method is an extension + var source = CSharpTestSource.Parse($$""" + struct S + { + static unsafe void Main() + { + S s = default; + S* p = &s; + {{invocation}} + } + } + + static class Ext + { + public static void M(this {{refKind}}S s) => throw null!; + } + """, "Program.cs", RegularWithInterceptors); + + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugExe); + CompileAndVerify(comp, verify: Verification.Fails); + + var model = comp.GetSemanticModel(source); + var node = source.GetRoot().DescendantNodes().OfType().Single(); + var locationSpecifier = model.GetInterceptableLocation(node)!; + + var interceptors = CSharpTestSource.Parse($$""" + #nullable enable + using System; + + static class Interceptors + { + {{locationSpecifier.GetInterceptsLocationAttributeSyntax()}} + public static void M1(this {{refKind}}S s) => Console.Write(1); + } + """, "Interceptors.cs", RegularWithInterceptors); + + var verifier = CompileAndVerify( + [source, interceptors, s_attributesTree], + options: TestOptions.UnsafeDebugExe, + verify: Verification.Fails, + expectedOutput: "1"); + verifier.VerifyDiagnostics(); + + comp = (CSharpCompilation)verifier.Compilation; + model = comp.GetSemanticModel(source); + var method = model.GetInterceptorMethod(node); + Assert.Equal($"void Interceptors.M1(this {refKind}S s)", method.ToTestDisplayString()); + } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index 44237e0c5c671..2d3ef41415b5f 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -14376,6 +14376,230 @@ .locals init (object V_0, //d "); } + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + public void DynamicConstruction10( + [CombinatorialValues(@"$""literal{d}""", @"$""literal"" + $""{d}""")] string expression, + [CombinatorialValues("object", "dynamic", "string")] string type) + { + var source = $$""" + using System; + using System.Runtime.CompilerServices; + using System.Text; + + dynamic d = "Hello world!"; + + Console.WriteLine(Interpolate({{expression}})); + + static string Interpolate(CustomInterpolationHandler text) + { + return text.ToString(); + } + + [InterpolatedStringHandler] + public ref struct CustomInterpolationHandler + { + private StringBuilder StringBuilder; + + public CustomInterpolationHandler(int literalLength, int formattedCount) + { + StringBuilder = new StringBuilder(); + } + + public void AppendLiteral(string text) + { + StringBuilder.Append(text); + } + + public void AppendFormatted({{type}} item) + { + StringBuilder.Append(item); + } + + public override string ToString() + { + return StringBuilder.ToString(); + } + } + """; + + var verifier = CompileAndVerify([source, InterpolatedStringHandlerAttribute], expectedOutput: "literalHello world!", targetFramework: TargetFramework.StandardAndCSharp); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("", type switch + { + "string" => """ + { + // Code size 110 (0x6e) + .maxstack 4 + .locals init (object V_0, //d + CustomInterpolationHandler V_1) + IL_0000: ldstr "Hello world!" + IL_0005: stloc.0 + IL_0006: ldloca.s V_1 + IL_0008: ldc.i4.7 + IL_0009: ldc.i4.1 + IL_000a: call "CustomInterpolationHandler..ctor(int, int)" + IL_000f: ldloca.s V_1 + IL_0011: ldstr "literal" + IL_0016: call "void CustomInterpolationHandler.AppendLiteral(string)" + IL_001b: ldloca.s V_1 + IL_001d: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0022: brtrue.s IL_0048 + IL_0024: ldc.i4.0 + IL_0025: ldtoken "string" + IL_002a: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_002f: ldtoken "Program" + IL_0034: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0039: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" + IL_003e: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0043: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0048: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_004d: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" + IL_0052: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0057: ldloc.0 + IL_0058: callvirt "string System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" + IL_005d: call "void CustomInterpolationHandler.AppendFormatted(string)" + IL_0062: ldloc.1 + IL_0063: call "string Program.<
$>g__Interpolate|0_0(CustomInterpolationHandler)" + IL_0068: call "void System.Console.WriteLine(string)" + IL_006d: ret + } + """, + _ => $$""" + { + // Code size 47 (0x2f) + .maxstack 3 + .locals init (object V_0, //d + CustomInterpolationHandler V_1) + IL_0000: ldstr "Hello world!" + IL_0005: stloc.0 + IL_0006: ldloca.s V_1 + IL_0008: ldc.i4.7 + IL_0009: ldc.i4.1 + IL_000a: call "CustomInterpolationHandler..ctor(int, int)" + IL_000f: ldloca.s V_1 + IL_0011: ldstr "literal" + IL_0016: call "void CustomInterpolationHandler.AppendLiteral(string)" + IL_001b: ldloca.s V_1 + IL_001d: ldloc.0 + IL_001e: call "void CustomInterpolationHandler.AppendFormatted({{type}})" + IL_0023: ldloc.1 + IL_0024: call "string Program.<
$>g__Interpolate|0_0(CustomInterpolationHandler)" + IL_0029: call "void System.Console.WriteLine(string)" + IL_002e: ret + } + """, + }); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + public void DynamicConstruction11([CombinatorialValues(@"$""literal{d}""", @"$""literal"" + $""{d}""")] string expression) + { + var source = $$""" + using System; + using System.Runtime.CompilerServices; + using System.Text; + + dynamic d = "Hello world!"; + + Console.WriteLine(Interpolate({{expression}})); + + static string Interpolate(CustomInterpolationHandler text) + { + return text.ToString(); + } + + [InterpolatedStringHandler] + public ref struct CustomInterpolationHandler + { + private StringBuilder StringBuilder; + + public CustomInterpolationHandler(int literalLength, int formattedCount) + { + StringBuilder = new StringBuilder(); + } + + public void AppendLiteral(string text) + { + StringBuilder.Append(text); + } + + public void AppendFormatted(T item) + { + StringBuilder.Append(item); + } + + public override string ToString() + { + return StringBuilder.ToString(); + } + } + """; + + CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp).VerifyDiagnostics( + // (7,31): error CS9230: Cannot perform a dynamic invocation on an expression with type 'CustomInterpolationHandler'. + // Console.WriteLine(Interpolate($"literal" + $"{d}")); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, expression).WithArguments("CustomInterpolationHandler").WithLocation(7, 31) + + ); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + public void DynamicConstruction12([CombinatorialValues(@"$""literal{d}""", @"$""literal"" + $""{d}""")] string expression) + { + var source = $$""" + using System; + using System.Runtime.CompilerServices; + using System.Text; + + dynamic d = "Hello world!"; + + Console.WriteLine(Interpolate({{expression}})); + + static string Interpolate(CustomInterpolationHandler text) + { + return text.ToString(); + } + + [InterpolatedStringHandler] + public ref struct CustomInterpolationHandler + { + private StringBuilder StringBuilder; + + public CustomInterpolationHandler(int literalLength, int formattedCount) + { + StringBuilder = new StringBuilder(); + } + + public void AppendLiteral(string text) + { + StringBuilder.Append(text); + } + + public void AppendFormatted(object item) + { + StringBuilder.Append(item); + } + + public void AppendFormatted(string item) + { + StringBuilder.Append(item); + } + + public override string ToString() + { + return StringBuilder.ToString(); + } + } + """; + + CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp).VerifyDiagnostics( + // (7,31): error CS9230: Cannot perform a dynamic invocation on an expression with type 'CustomInterpolationHandler'. + // Console.WriteLine(Interpolate($"literal" + $"{d}")); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, expression).WithArguments("CustomInterpolationHandler").WithLocation(7, 31) + ); + } + [Theory] [InlineData(@"$""{s}""")] [InlineData(@"$""{s}"" + $""""")] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs index eb0310f80ca82..06055448cc8b6 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/IteratorTests.cs @@ -149,7 +149,7 @@ private async IAsyncEnumerable GetValuesAsync() var expectedDiagnostics = new[] { - // (23,17): warning CS9230: 'yield return' should not be used in the body of a lock statement + // (23,17): warning CS9237: 'yield return' should not be used in the body of a lock statement // yield return i; Diagnostic(ErrorCode.WRN_BadYieldInLock, "yield").WithLocation(23, 17) }; @@ -213,7 +213,7 @@ static IEnumerable GetValues(object obj) var expectedDiagnostics = new[] { - // (24,13): warning CS9230: 'yield return' should not be used in the body of a lock statement + // (24,13): warning CS9237: 'yield return' should not be used in the body of a lock statement // yield return i; Diagnostic(ErrorCode.WRN_BadYieldInLock, "yield").WithLocation(24, 13) }; @@ -260,10 +260,10 @@ IEnumerable local() """; CreateCompilation(source).VerifyDiagnostics( - // (10,13): warning CS9230: 'yield return' should not be used in the body of a lock statement + // (10,13): warning CS9237: 'yield return' should not be used in the body of a lock statement // yield return 2; Diagnostic(ErrorCode.WRN_BadYieldInLock, "yield").WithLocation(10, 13), - // (20,21): warning CS9230: 'yield return' should not be used in the body of a lock statement + // (20,21): warning CS9237: 'yield return' should not be used in the body of a lock statement // yield return 4; Diagnostic(ErrorCode.WRN_BadYieldInLock, "yield").WithLocation(20, 21)); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs index 08698e915b7b4..68074bbd50312 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs @@ -962,7 +962,7 @@ struct S S(object obj) : this() { } }"; verify(source, expectedAnalyzedKeys: new[] { ".ctor" }, - // (6,5): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (6,5): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // S(object obj) : this() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "F1").WithLocation(6, 5) ); @@ -1083,7 +1083,7 @@ public void AnalyzeMethodsInEnabledContextOnly_05() static object P2 { get; set; } }"; verify(source, expectedAnalyzedKeys: new[] { ".cctor" }, - // (6,19): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (6,19): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // static object P2 { get; set; } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P2").WithArguments("property", "P2").WithLocation(6, 19)); @@ -1096,7 +1096,7 @@ public void AnalyzeMethodsInEnabledContextOnly_05() static object P2 { get; set; } }"; verify(source, expectedAnalyzedKeys: new[] { ".ctor" }, - // (4,12): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (4,12): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // object P1 { get; set; } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P1").WithArguments("property", "P1").WithLocation(4, 12)); @@ -1109,7 +1109,7 @@ public void AnalyzeMethodsInEnabledContextOnly_05() object P3 => 3; }"; verify(source, expectedAnalyzedKeys: new[] { ".ctor", "get_P2", "get_P3", "set_P2" }, - // (4,12): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (4,12): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // object P1 { get; } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P1").WithArguments("property", "P1").WithLocation(4, 12)); @@ -1124,7 +1124,7 @@ class Program static event D E2; }"; verify(source, expectedAnalyzedKeys: new[] { ".cctor" }, - // (8,20): warning CS8618: Non-nullable event 'E2' must contain a non-null value when exiting constructor. Consider declaring the event as nullable. + // (8,20): warning CS8618: Non-nullable event 'E2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the event as nullable. // static event D E2; Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "E2").WithArguments("event", "E2").WithLocation(8, 20)); @@ -1139,7 +1139,7 @@ class Program static event D E2; }"; verify(source, expectedAnalyzedKeys: new[] { ".ctor" }, - // (6,13): warning CS8618: Non-nullable event 'E1' must contain a non-null value when exiting constructor. Consider declaring the event as nullable. + // (6,13): warning CS8618: Non-nullable event 'E1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the event as nullable. // event D E1; Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "E1").WithArguments("event", "E1").WithLocation(6, 13)); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index c418ef1be80e0..81d33246c8d68 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -30965,10 +30965,10 @@ public void Init(T t) "; var comp = CreateNullableCompilation(new[] { source, NotNullAttributeDefinition, MemberNotNullAttributeDefinition, DisallowNullAttributeDefinition }); comp.VerifyDiagnostics( - // (9,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (9,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C() { } // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "Prop").WithLocation(9, 12), - // (10,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C(T t) { Prop = t; } // 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "Prop").WithLocation(10, 12), // (11,38): warning CS8602: Dereference of a possibly null reference. @@ -31006,10 +31006,10 @@ public void Init(T t) "; var comp = CreateNullableCompilation(new[] { source, NotNullAttributeDefinition, MemberNotNullAttributeDefinition, DisallowNullAttributeDefinition }); comp.VerifyDiagnostics( - // (9,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (9,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C() { } // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("field", "field").WithLocation(9, 12), - // (10,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (10,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C(T t) { field = t; } // 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("field", "field").WithLocation(10, 12), // (11,39): warning CS8602: Dereference of a possibly null reference. @@ -31078,10 +31078,10 @@ public C3(int i) // We expect no warnings on `C2(string?)` because `C2.F`'s getter and setter are not linked. var comp = CreateNullableCompilation(new[] { source, NotNullAttributeDefinition, DisallowNullAttributeDefinition }); comp.VerifyDiagnostics( - // (9,12): warning CS8618: Non-nullable property 'F1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (9,12): warning CS8618: Non-nullable property 'F1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C1() { } // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C1").WithArguments("property", "F1").WithLocation(9, 12), - // (10,12): warning CS8618: Non-nullable property 'F1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'F1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C1(string? F1) // 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C1").WithArguments("property", "F1").WithLocation(10, 12), // (12,19): warning CS8601: Possible null reference assignment. @@ -31149,19 +31149,19 @@ public C3(int i) "; var comp = CreateNullableCompilation(new[] { source, NotNullAttributeDefinition, DisallowNullAttributeDefinition }); comp.VerifyDiagnostics( - // (9,12): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (9,12): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C1() { } // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C1").WithArguments("field", "F").WithLocation(9, 12), - // (10,12): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (10,12): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C1(string? F) // 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C1").WithArguments("field", "F").WithLocation(10, 12), // (12,18): warning CS8601: Possible null reference assignment. // this.F = F; // 3 Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "F").WithLocation(12, 18), - // (25,12): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (25,12): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C2() { } // 4 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C2").WithArguments("field", "F").WithLocation(25, 12), - // (26,12): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (26,12): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C2(string? F) // 5 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C2").WithArguments("field", "F").WithLocation(26, 12), // (44,18): warning CS8601: Possible null reference assignment. @@ -31245,10 +31245,10 @@ public void M1(T t) { } // it seems like property state should be NotNull after assigning a MaybeNull to the property. // https://github.com/dotnet/roslyn/issues/49964 comp.VerifyDiagnostics( - // (8,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (8,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C() // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "Prop").WithLocation(8, 12), - // (12,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (12,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C(T t) // 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "Prop").WithLocation(12, 12), // (19,9): warning CS8602: Dereference of a possibly null reference. @@ -33905,7 +33905,7 @@ static void X2(ref T location, T value) where T : class? { } "; var comp = CreateCompilation(new[] { source, NotNullIfNotNullAttributeDefinition }); comp.VerifyEmitDiagnostics( - // (14,5): warning CS8618: Non-nullable field 'f2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (14,5): warning CS8618: Non-nullable field 'f2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // C() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C", isSuppressed: false).WithArguments("field", "f2").WithLocation(14, 5), // (18,16): warning CS8601: Possible null reference assignment. @@ -45895,13 +45895,13 @@ public class CStruct where TStruct : struct }"; var comp = CreateNullableCompilation(new[] { lib_cs, NotNullAttributeDefinition }); comp.VerifyDiagnostics( - // (5,5): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (5,5): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // COpen() // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "COpen").WithArguments("property", "P1").WithLocation(5, 5), // (7,14): warning CS8601: Possible null reference assignment. // P1 = default; // 2 Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "default").WithLocation(7, 14), - // (14,5): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (14,5): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // CClass() // 3 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "CClass").WithArguments("property", "P2").WithLocation(14, 5), // (16,14): warning CS8625: Cannot convert null literal to non-nullable reference type. @@ -60808,7 +60808,7 @@ struct S1 // (12,14): warning CS8601: Possible null reference assignment. // P1 = x1; // 2 Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "x1").WithLocation(12, 14), - // (10,12): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C1(C1? x1) // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C1").WithArguments("property", "P1").WithLocation(10, 12), // (22,14): warning CS8600: Converting null literal or possible null value to non-nullable type. @@ -60895,7 +60895,7 @@ struct S1 // (12,14): warning CS8601: Possible null reference assignment. // P1 = x1; // 2 Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "x1").WithLocation(12, 14), - // (10,12): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C1(C0? x1) // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C1").WithArguments("property", "P1").WithLocation(10, 12), // (34,14): warning CS8600: Converting null literal or possible null value to non-nullable type. @@ -73723,7 +73723,7 @@ static void F4(A? a) { } var comp = CreateCompilation(new[] { source }, options: WithNullableEnable()); // https://github.com/dotnet/roslyn/issues/31018: Report warnings for // 3 and // 4. comp.VerifyDiagnostics( - // (6,30): warning CS8618: Non-nullable event 'E' must contain a non-null value when exiting constructor. Consider declaring the event as nullable. + // (6,30): warning CS8618: Non-nullable event 'E' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the event as nullable. // event Action> E; Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "E").WithArguments("event", "E").WithLocation(6, 30), // (10,17): warning CS8622: Nullability of reference types in type of parameter 'a' of 'void B.F1(A a)' doesn't match the target delegate 'Action>' (possibly because of nullability attributes). @@ -121363,7 +121363,7 @@ static void Test(string? s) }"; CreateCompilation(source, options: WithNullableEnable()).VerifyDiagnostics( - // (5,12): warning CS8618: Non-nullable field 'FieldWithInferredAnnotation' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (5,12): warning CS8618: Non-nullable field 'FieldWithInferredAnnotation' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public T FieldWithInferredAnnotation; Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "FieldWithInferredAnnotation").WithArguments("field", "FieldWithInferredAnnotation").WithLocation(5, 12), // (19,5): warning CS8602: Dereference of a possibly null reference. @@ -130261,7 +130261,7 @@ public C() // 1 var comp = CreateCompilation(source, options: WithNullableEnable()); comp.VerifyDiagnostics( - // (6,12): warning CS8618: Non-nullable field 'x' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (6,12): warning CS8618: Non-nullable field 'x' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C() // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("field", "x").WithLocation(6, 12), // (14,19): warning CS8625: Cannot convert null literal to non-nullable reference type. @@ -130295,7 +130295,7 @@ public C() // 1 var comp = CreateCompilation(source, options: WithNullableEnable()); comp.VerifyDiagnostics( - // (7,12): warning CS8618: Non-nullable field 'x' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (7,12): warning CS8618: Non-nullable field 'x' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C() // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("field", "x").WithLocation(7, 12), // (15,19): warning CS8625: Cannot convert null literal to non-nullable reference type. @@ -153546,27 +153546,18 @@ public override void M1(C x) class C {} "); comp.VerifyDiagnostics( - // (11,32): error CS0305: Using the generic type 'C' requires 1 type arguments + // (11,26): error CS0115: 'B.M1(C)': no suitable method found to override // public override void M1(C x) - Diagnostic(ErrorCode.ERR_BadArity, "C x").WithArguments("C", "type", "1").WithLocation(11, 32), - // (11,54): error CS1003: Syntax error, ',' expected + Diagnostic(ErrorCode.ERR_OverrideNotExpected, "M1").WithArguments("B.M1(C)").WithLocation(11, 26), + // (11,54): error CS1003: Syntax error, '>' expected // public override void M1(C x) - Diagnostic(ErrorCode.ERR_SyntaxError, "x").WithArguments(",").WithLocation(11, 54), - // (11,54): error CS0246: The type or namespace name 'x' could not be found (are you missing a using directive or an assembly reference?) + Diagnostic(ErrorCode.ERR_SyntaxError, "x").WithArguments(">").WithLocation(11, 54), + // (11,54): error CS0453: The type 'T?' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Nullable' // public override void M1(C x) - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "x").WithArguments("x").WithLocation(11, 54), - // (11,55): error CS1003: Syntax error, '>' expected + Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "x").WithArguments("System.Nullable", "T", "T?").WithLocation(11, 54), + // (11,54): error CS0453: The type 'T' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Nullable' // public override void M1(C x) - Diagnostic(ErrorCode.ERR_SyntaxError, ")").WithArguments(">").WithLocation(11, 55), - // (11,55): error CS1001: Identifier expected - // public override void M1(C x) - Diagnostic(ErrorCode.ERR_IdentifierExpected, ")").WithLocation(11, 55), - // (11,55): error CS0453: The type 'T?' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Nullable' - // public override void M1(C x) - Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "").WithArguments("System.Nullable", "T", "T?").WithLocation(11, 55), - // (11,55): error CS0453: The type 'T' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Nullable' - // public override void M1(C x) - Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "").WithArguments("System.Nullable", "T", "T").WithLocation(11, 55) + Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "x").WithArguments("System.Nullable", "T", "T").WithLocation(11, 54) ); } @@ -154685,10 +154676,10 @@ public class Nested // (5,24): warning CS8625: Cannot convert null literal to non-nullable reference type. // c.Property.Property2 = null; Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(5, 24), - // (13,19): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (13,19): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Nested Property { get; set; } // implicitly means C.Nested Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Property").WithArguments("property", "Property").WithLocation(13, 19), - // (16,18): warning CS8618: Non-nullable property 'Property2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (16,18): warning CS8618: Non-nullable property 'Property2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public U Property2 { get; set; } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Property2").WithArguments("property", "Property2").WithLocation(16, 18) ); @@ -154721,10 +154712,10 @@ public class Nested // (5,24): warning CS8625: Cannot convert null literal to non-nullable reference type. // c.Property.Property2 = null; Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(5, 24), - // (13,24): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (13,24): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C.Nested Property { get; set; } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Property").WithArguments("property", "Property").WithLocation(13, 24), - // (16,18): warning CS8618: Non-nullable property 'Property2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (16,18): warning CS8618: Non-nullable property 'Property2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public U Property2 { get; set; } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Property2").WithArguments("property", "Property2").WithLocation(16, 18) ); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs index d74969e7e8ad7..196bf20382478 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs @@ -6,6 +6,8 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -4158,5 +4160,141 @@ Element Values(2): Assert.Equal(SpecialType.System_Int32, typeInfo.Type.SpecialType); Assert.Equal("I", typeInfo.ConvertedType.ToDisplayString()); } + + [Fact] + public void DynamicInvocationOnRefStructs() + { + var source = """ + using System.Collections; + using System.Collections.Generic; + + dynamic d = null; + S s = new S() { d }; + + ref struct S : IEnumerable + { + public IEnumerator GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; + public void Add(T t) => throw null; + } + """; + + CreateCompilation(source).VerifyDiagnostics( + // (5,15): error CS1922: Cannot initialize type 'S' with a collection initializer because it does not implement 'System.Collections.IEnumerable' + // S s = new S() { d }; + Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{ d }").WithArguments("S").WithLocation(5, 15), + // (7,16): error CS8343: 'S': ref structs cannot implement interfaces + // ref struct S : IEnumerable + Diagnostic(ErrorCode.ERR_RefStructInterfaceImpl, "IEnumerable").WithArguments("S").WithLocation(7, 16) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72916")] + public void RefReturning_Indexer() + { + var source = """ + public class C + { + public static void Main() + { + var c = new C() { [1] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72916")] + public void RefReturning_Property() + { + var source = """ + public class C + { + public static void Main() + { + var c = new C() { P = 2 }; + System.Console.WriteLine(c.P); + } + + int _test1 = 0; + ref int P + { + get => ref _test1; + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var propertyAccess = tree.GetRoot().DescendantNodes().OfType().First().Left; + var symbolInfo = model.GetSymbolInfo(propertyAccess); + AssertEx.Equal("ref System.Int32 C.P { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(propertyAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(propertyAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)propertyAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs index b31d4f8291d41..b4990d8031bfd 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs @@ -3192,7 +3192,7 @@ static void M() TestOperatorKinds(code); } - private void TestBoundTree(string source, System.Func>, IEnumerable> query) + private static void TestBoundTree(string source, System.Func>, IEnumerable> query) { // The mechanism of this test is: we build the bound tree for the code passed in and then extract // from it the nodes that describe the operators. We then compare the description of @@ -3217,7 +3217,7 @@ private void TestBoundTree(string source, System.Func from edge in edges @@ -3260,7 +3260,7 @@ select string.Join(" ", from child in node.Children }))); } - private void TestTypes(string source) + internal static void TestTypes(string source) { TestBoundTree(source, edges => from edge in edges @@ -3289,7 +3289,7 @@ private static string FormatTypeArgumentList(ImmutableArray return s + ">"; } - private void TestDynamicMemberAccessCore(string source) + internal static void TestDynamicMemberAccessCore(string source) { TestBoundTree(source, edges => from edge in edges diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs index 40cf855b54633..1ed435e9df270 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs @@ -309,7 +309,8 @@ static void Main() } }"; var comp = CreateCompilation(source); - comp.VerifyDiagnostics(); + var diagnostics = comp.GetDiagnostics().Where(d => d is not { Severity: DiagnosticSeverity.Info, Code: (int)ErrorCode.INF_TooManyBoundLambdas }); + diagnostics.Verify(); } /// @@ -383,6 +384,73 @@ static void F(IEnumerable x) comp.VerifyDiagnostics(); } + [Fact] + public void NestedLambdas_MethodBody() + { + var source = """ + #pragma warning disable 649 + using System.Collections.Generic; + using System.Linq; + class Container + { + public IEnumerable Items; + public int Value; + } + class Program + { + static void Main() + { + var list = new List(); + _ = list.Sum( + a => a.Items.Sum( + b => b.Items.Sum( + c => c.Value))); + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (16,19): info CS9236: Compiling requires binding the lambda expression at least 100 times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + // b => b.Items.Sum( + Diagnostic(ErrorCode.INF_TooManyBoundLambdas, "=>").WithArguments("100").WithLocation(16, 19), + // (17,23): info CS9236: Compiling requires binding the lambda expression at least 1300 times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + // c => c.Value))); + Diagnostic(ErrorCode.INF_TooManyBoundLambdas, "=>").WithArguments("1300").WithLocation(17, 23)); + } + + [Fact] + public void NestedLambdas_FieldInitializer() + { + var source = """ + #pragma warning disable 649 + using System.Collections.Generic; + using System.Linq; + class Container + { + public IEnumerable Items; + public int Value; + } + class Program(List list) + { + int _f = list.Sum( + a => a.Items.Sum( + b => b.Items.Sum( + c => c.Value))); + static void Main() + { + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (13,15): info CS9236: Compiling requires binding the lambda expression at least 100 times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + // b => b.Items.Sum( + Diagnostic(ErrorCode.INF_TooManyBoundLambdas, "=>").WithArguments("100").WithLocation(13, 15), + // (14,19): info CS9236: Compiling requires binding the lambda expression at least 1300 times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + // c => c.Value))); + Diagnostic(ErrorCode.INF_TooManyBoundLambdas, "=>").WithArguments("1300").WithLocation(14, 19)); + } + [ConditionalFact(typeof(NoIOperationValidation), Reason = "Timeouts")] [WorkItem(48886, "https://github.com/dotnet/roslyn/issues/48886")] public void ArrayInitializationAnonymousTypes() @@ -855,7 +923,10 @@ public static void F(this object o, C{{i}} c, System.Action a) { } comp.VerifyDiagnostics( // (6,11): error CS0121: The call is ambiguous between the following methods or properties: 'E0.F(object, C0, Action)' and 'E1.F(object, C1, Action)' // o.F(null, c => o.F(c, null)); - Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("E0.F(object, C0, System.Action)", "E1.F(object, C1, System.Action)").WithLocation(6, 11)); + Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("E0.F(object, C0, System.Action)", "E1.F(object, C1, System.Action)").WithLocation(6, 11), + // (6,21): info CS9236: Compiling requires binding the lambda expression at least 1000 times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + // o.F(null, c => o.F(c, null)); + Diagnostic(ErrorCode.INF_TooManyBoundLambdas, "=>").WithArguments("1000").WithLocation(6, 21)); } [ConditionalFact(typeof(IsRelease))] @@ -893,6 +964,9 @@ public static void F(this object o, C{{i}} c, System.Action a) { } string source = builder.ToString(); var comp = CreateCompilation(source); comp.VerifyDiagnostics( + // (7,18): info CS9236: Compiling requires binding the lambda expression at least 1000 times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + // o.F(c, c => { o.F( }); + Diagnostic(ErrorCode.INF_TooManyBoundLambdas, "=>").WithArguments("1000").WithLocation(7, 18), // (7,25): error CS1501: No overload for method 'F' takes 0 arguments // o.F(c, c => { o.F( }); Diagnostic(ErrorCode.ERR_BadArgCount, "F").WithArguments("F", "0").WithLocation(7, 25), diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs index 1e85496ccccf4..57fade24f3da2 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs @@ -7607,10 +7607,18 @@ static void Main() "; var comp = CreateCompilationWithMscorlib40AndSystemCore(source); comp.VerifyDiagnostics( - // (8,44): error CS0121: The call is ambiguous between the following methods or properties: 'C.M(System.Func)' and 'C.M(System.Func)' - // M(a => M(b => M(c => M(d => M(e => M(f => a)))))); - Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M(System.Func)", "C.M(System.Func)").WithLocation(8, 44) - ); + // (8,34): info CS9236: Compiling requires binding the lambda expression at least 200 times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + // M(a => M(b => M(c => M(d => M(e => M(f => a)))))); + Diagnostic(ErrorCode.INF_TooManyBoundLambdas, "=>").WithArguments("200").WithLocation(8, 34), + // (8,41): info CS9236: Compiling requires binding the lambda expression at least 1000 times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + // M(a => M(b => M(c => M(d => M(e => M(f => a)))))); + Diagnostic(ErrorCode.INF_TooManyBoundLambdas, "=>").WithArguments("1000").WithLocation(8, 41), + // (8,44): error CS0121: The call is ambiguous between the following methods or properties: 'C.M(Func)' and 'C.M(Func)' + // M(a => M(b => M(c => M(d => M(e => M(f => a)))))); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M(System.Func)", "C.M(System.Func)").WithLocation(8, 44), + // (8,48): info CS9236: Compiling requires binding the lambda expression at least 4000 times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments. + // M(a => M(b => M(c => M(d => M(e => M(f => a)))))); + Diagnostic(ErrorCode.INF_TooManyBoundLambdas, "=>").WithArguments("4000").WithLocation(8, 48)); } [Fact, WorkItem(30, "https://roslyn.codeplex.com/workitem/30")] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 9059ff517d961..e52cd58f53ebe 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -760,6 +760,12 @@ ref struct S1 // (22,18): error CS8352: Cannot use variable 'local' in this context because it may expose referenced variables outside of their declaration scope // global = local && global; Diagnostic(ErrorCode.ERR_EscapeVariable, "local").WithArguments("local").WithLocation(22, 18), + // (22,18): error CS8347: Cannot use a result of 'S1.operator &(S1, S1)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // global = local && global; + Diagnostic(ErrorCode.ERR_EscapeCall, "local && global").WithArguments("S1.operator &(S1, S1)", "x").WithLocation(22, 18), + // (25,16): error CS8347: Cannot use a result of 'S1.operator |(S1, S1)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // return global || local; + Diagnostic(ErrorCode.ERR_EscapeCall, "global || local").WithArguments("S1.operator |(S1, S1)", "y").WithLocation(25, 16), // (25,26): error CS8352: Cannot use variable 'local' in this context because it may expose referenced variables outside of their declaration scope // return global || local; Diagnostic(ErrorCode.ERR_EscapeVariable, "local").WithArguments("local").WithLocation(25, 26) @@ -7244,5 +7250,1376 @@ static void M(ReadOnlySpan s) var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); comp.VerifyDiagnostics(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_RefStruct_Explicit() + { + var source = """ + class C + { + S M1() + { + S s; + s = (S)100; // 1 + return s; + } + + S M2() + { + return (S)200; // 2 + } + + S M3(in int x) + { + S s; + s = (S)x; // 3 + return s; + } + + S M4(in int x) + { + return (S)x; + } + + S M4s(scoped in int x) + { + return (S)x; // 4 + } + + S M5(in int x) + { + S s = (S)x; + return s; + } + + S M5s(scoped in int x) + { + S s = (S)x; + return s; // 5 + } + + S M6() + { + S s = (S)300; + return s; // 6 + } + + void M7(in int x) + { + scoped S s; + s = (S)x; + s = (S)100; + } + } + + ref struct S + { + public static explicit operator S(in int x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (6,13): error CS8347: Cannot use a result of 'S.explicit operator S(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = (S)100; // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "(S)100").WithArguments("S.explicit operator S(in int)", "x").WithLocation(6, 13), + // (6,16): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // s = (S)100; // 1 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "100").WithLocation(6, 16), + // (12,16): error CS8347: Cannot use a result of 'S.explicit operator S(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return (S)200; // 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "(S)200").WithArguments("S.explicit operator S(in int)", "x").WithLocation(12, 16), + // (12,19): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return (S)200; // 2 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "200").WithLocation(12, 19), + // (18,13): error CS8347: Cannot use a result of 'S.explicit operator S(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = (S)x; // 3 + Diagnostic(ErrorCode.ERR_EscapeCall, "(S)x").WithArguments("S.explicit operator S(in int)", "x").WithLocation(18, 13), + // (18,16): error CS9077: Cannot return a parameter by reference 'x' through a ref parameter; it can only be returned in a return statement + // s = (S)x; // 3 + Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "x").WithArguments("x").WithLocation(18, 16), + // (29,16): error CS8347: Cannot use a result of 'S.explicit operator S(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return (S)x; // 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "(S)x").WithArguments("S.explicit operator S(in int)", "x").WithLocation(29, 16), + // (29,19): error CS9075: Cannot return a parameter by reference 'x' because it is scoped to the current method + // return (S)x; // 4 + Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "x").WithArguments("x").WithLocation(29, 19), + // (41,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 5 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(41, 16), + // (47,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 6 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(47, 16)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_RefStruct_Implicit() + { + var source = """ + class C + { + S M1() + { + S s; + s = 100; // 1 + return s; + } + + S M2() + { + return 200; // 2 + } + + S M3(in int x) + { + S s; + s = x; // 3 + return s; + } + + S M4(in int x) + { + return x; + } + + S M4s(scoped in int x) + { + return x; // 4 + } + + S M5(in int x) + { + S s = x; + return s; + } + + S M5s(scoped in int x) + { + S s = x; + return s; // 5 + } + + S M6() + { + S s = 300; + return s; // 6 + } + + void M7(in int x) + { + scoped S s; + s = x; + s = 100; + } + } + + ref struct S + { + public static implicit operator S(in int x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (6,13): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // s = 100; // 1 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "100").WithLocation(6, 13), + // (6,13): error CS8347: Cannot use a result of 'S.implicit operator S(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = 100; // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "100").WithArguments("S.implicit operator S(in int)", "x").WithLocation(6, 13), + // (12,16): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return 200; // 2 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "200").WithLocation(12, 16), + // (12,16): error CS8347: Cannot use a result of 'S.implicit operator S(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return 200; // 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "200").WithArguments("S.implicit operator S(in int)", "x").WithLocation(12, 16), + // (18,13): error CS9077: Cannot return a parameter by reference 'x' through a ref parameter; it can only be returned in a return statement + // s = x; // 3 + Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "x").WithArguments("x").WithLocation(18, 13), + // (18,13): error CS8347: Cannot use a result of 'S.implicit operator S(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = x; // 3 + Diagnostic(ErrorCode.ERR_EscapeCall, "x").WithArguments("S.implicit operator S(in int)", "x").WithLocation(18, 13), + // (29,16): error CS9075: Cannot return a parameter by reference 'x' because it is scoped to the current method + // return x; // 4 + Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "x").WithArguments("x").WithLocation(29, 16), + // (29,16): error CS8347: Cannot use a result of 'S.implicit operator S(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return x; // 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "x").WithArguments("S.implicit operator S(in int)", "x").WithLocation(29, 16), + // (41,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 5 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(41, 16), + // (47,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 6 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(47, 16)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_RefStructArgument() + { + var source = """ + class C + { + S2 M1() + { + int x = 1; + S1 s1 = (S1)x; + return (S2)s1; // 1 + } + } + + ref struct S1 + { + public static implicit operator S1(in int x) => throw null; + } + + ref struct S2 + { + public static implicit operator S2(S1 s1) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (7,16): error CS8347: Cannot use a result of 'S2.implicit operator S2(S1)' in this context because it may expose variables referenced by parameter 's1' outside of their declaration scope + // return (S2)s1; // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "(S2)s1").WithArguments("S2.implicit operator S2(S1)", "s1").WithLocation(7, 16), + // (7,20): error CS8352: Cannot use variable 's1' in this context because it may expose referenced variables outside of their declaration scope + // return (S2)s1; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s1").WithArguments("s1").WithLocation(7, 20)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_RefStructArgument_ScopedIn() + { + var source = """ + class C + { + S2 M1() + { + int x = 1; + S1 s1 = (S1)x; + return (S2)s1; + } + } + + ref struct S1 + { + public static implicit operator S1(scoped in int x) => throw null; + } + + ref struct S2 + { + public static implicit operator S2(S1 s1) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_RegularStruct() + { + var source = """ + S s; + s = (S)100; + + struct S + { + public static explicit operator S(in int x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_StandardImplicitConversion_Input() + { + var source = """ + class C + { + S M1() + { + S s; + s = 100; // 1 + return s; + } + + S M2() + { + return 200; // 2 + } + + S M3(in int x) + { + S s; + s = x; // 3 + return s; + } + + S M4(in int x) + { + return x; // 4 + } + + S M4s(scoped in int x) + { + return x; // 5 + } + + S M5(in int x) + { + S s = x; + return s; // 6 + } + + S M5s(scoped in int x) + { + S s = x; + return s; // 7 + } + + S M6() + { + S s = 300; + return s; // 8 + } + } + + ref struct S + { + public static implicit operator S(in int? x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (6,13): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // s = 100; // 1 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "100").WithLocation(6, 13), + // (6,13): error CS8347: Cannot use a result of 'S.implicit operator S(in int?)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = 100; // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "100").WithArguments("S.implicit operator S(in int?)", "x").WithLocation(6, 13), + // (12,16): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return 200; // 2 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "200").WithLocation(12, 16), + // (12,16): error CS8347: Cannot use a result of 'S.implicit operator S(in int?)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return 200; // 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "200").WithArguments("S.implicit operator S(in int?)", "x").WithLocation(12, 16), + // (18,13): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // s = x; // 3 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "x").WithLocation(18, 13), + // (18,13): error CS8347: Cannot use a result of 'S.implicit operator S(in int?)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = x; // 3 + Diagnostic(ErrorCode.ERR_EscapeCall, "x").WithArguments("S.implicit operator S(in int?)", "x").WithLocation(18, 13), + // (24,16): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return x; // 4 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "x").WithLocation(24, 16), + // (24,16): error CS8347: Cannot use a result of 'S.implicit operator S(in int?)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return x; // 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "x").WithArguments("S.implicit operator S(in int?)", "x").WithLocation(24, 16), + // (29,16): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return x; // 5 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "x").WithLocation(29, 16), + // (29,16): error CS8347: Cannot use a result of 'S.implicit operator S(in int?)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return x; // 5 + Diagnostic(ErrorCode.ERR_EscapeCall, "x").WithArguments("S.implicit operator S(in int?)", "x").WithLocation(29, 16), + // (35,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 6 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(35, 16), + // (41,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 7 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(41, 16), + // (47,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 8 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(47, 16)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_StandardImplicitConversion_Output() + { + var source = """ + object o; + o = (S)100; + + struct S + { + public static explicit operator S(in int x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_StandardImplicitConversion_Both() + { + var source = """ + object o; + int x = 100; + o = (S)x; + + struct S + { + public static explicit operator S(in int? x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_Call() + { + var source = """ + class C + { + S M1(int x) + { + return M2(x); + } + + S M2(S s) => s; + } + + ref struct S + { + public static implicit operator S(in int x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (5,16): error CS8347: Cannot use a result of 'C.M2(S)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // return M2(x); + Diagnostic(ErrorCode.ERR_EscapeCall, "M2(x)").WithArguments("C.M2(S)", "s").WithLocation(5, 16), + // (5,19): error CS8166: Cannot return a parameter by reference 'x' because it is not a ref parameter + // return M2(x); + Diagnostic(ErrorCode.ERR_RefReturnParameter, "x").WithArguments("x").WithLocation(5, 19), + // (5,19): error CS8347: Cannot use a result of 'S.implicit operator S(in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return M2(x); + Diagnostic(ErrorCode.ERR_EscapeCall, "x").WithArguments("S.implicit operator S(in int)", "x").WithLocation(5, 19)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_RefReturn_01() + { + var source = """ + class C + { + static ref readonly int M1(int x) + { + return ref M2(x); + } + + static ref readonly int M2(in S s) => throw null; + } + + struct S + { + public static implicit operator S(in int x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (5,20): error CS8347: Cannot use a result of 'C.M2(in S)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // return ref M2(x); + Diagnostic(ErrorCode.ERR_EscapeCall, "M2(x)").WithArguments("C.M2(in S)", "s").WithLocation(5, 20), + // (5,23): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return ref M2(x); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "x").WithLocation(5, 23)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_RefReturn_02() + { + var source = """ + class C + { + static ref readonly int M1(int x) + { + return ref M2(x); + } + + static ref readonly int M2(in S s) => throw null; + } + + struct S + { + public static implicit operator ref S(in int x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (5,20): error CS8347: Cannot use a result of 'C.M2(in S)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // return ref M2(x); + Diagnostic(ErrorCode.ERR_EscapeCall, "M2(x)").WithArguments("C.M2(in S)", "s").WithLocation(5, 20), + // (5,23): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return ref M2(x); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "x").WithLocation(5, 23), + // (13,37): error CS1073: Unexpected token 'ref' + // public static implicit operator ref S(in int x) => throw null; + Diagnostic(ErrorCode.ERR_UnexpectedToken, "ref").WithArguments("ref").WithLocation(13, 37)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedCast_RefReturn_IL() + { + // public struct S + // { + // public static implicit operator ref readonly S(in int x) => throw null; + // } + var ilSource = """ + .class public sequential ansi sealed beforefieldinit S extends System.ValueType + { + .pack 0 + .size 1 + + .method public hidebysig specialname static valuetype S& modreq(System.Runtime.InteropServices.InAttribute) op_Implicit ( + [in] int32& x + ) cil managed + { + .param [1] + .custom instance void System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( + 01 00 00 00 + ) + + .maxstack 1 + .locals init ( + [0] valuetype S + ) + + ldnull + throw + } + } + + .class public auto ansi sealed beforefieldinit System.Runtime.InteropServices.InAttribute extends System.Object + { + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed + { + .maxstack 8 + ret + } + } + + .class public auto ansi sealed beforefieldinit System.Runtime.CompilerServices.IsReadOnlyAttribute extends System.Object + { + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed + { + .maxstack 8 + ret + } + } + """; + + var source = """ + class C + { + static ref readonly int M1(int x) + { + return ref M2(x); + } + + static ref readonly int M2(in S s) => throw null; + } + """; + CreateCompilationWithIL(source, ilSource).VerifyDiagnostics( + // (5,20): error CS8347: Cannot use a result of 'C.M2(in S)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // return ref M2(x); + Diagnostic(ErrorCode.ERR_EscapeCall, "M2(x)").WithArguments("C.M2(in S)", "s").WithLocation(5, 20), + // (5,23): error CS0570: 'S.implicit operator S(in int)' is not supported by the language + // return ref M2(x); + Diagnostic(ErrorCode.ERR_BindToBogus, "x").WithArguments("S.implicit operator S(in int)").WithLocation(5, 23), + // (5,23): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return ref M2(x); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "x").WithLocation(5, 23)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct() + { + var source = """ + class C + { + S M1() + { + S s; + s = 100 + default(S); // 1 + return s; + } + + S M2() + { + return 200 + default(S); // 2 + } + + S M3(in int x) + { + S s; + s = x + default(S); // 3 + return s; + } + + S M4(in int x) + { + return x + default(S); + } + + S M4s(scoped in int x) + { + return x + default(S); // 4 + } + + S M5(in int x) + { + S s = x + default(S); + return s; + } + + S M5s(scoped in int x) + { + S s = x + default(S); + return s; // 5 + } + + S M6() + { + S s = 300 + default(S); + return s; // 6 + } + + void M7(in int x) + { + scoped S s; + s = x + default(S); + s = 100 + default(S); + } + } + + ref struct S + { + public static S operator+(in int x, S y) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (6,13): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // s = 100 + default(S); // 1 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "100").WithLocation(6, 13), + // (6,13): error CS8347: Cannot use a result of 'S.operator +(in int, S)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = 100 + default(S); // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "100 + default(S)").WithArguments("S.operator +(in int, S)", "x").WithLocation(6, 13), + // (12,16): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return 200 + default(S); // 2 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "200").WithLocation(12, 16), + // (12,16): error CS8347: Cannot use a result of 'S.operator +(in int, S)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return 200 + default(S); // 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "200 + default(S)").WithArguments("S.operator +(in int, S)", "x").WithLocation(12, 16), + // (18,13): error CS9077: Cannot return a parameter by reference 'x' through a ref parameter; it can only be returned in a return statement + // s = x + default(S); // 3 + Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "x").WithArguments("x").WithLocation(18, 13), + // (18,13): error CS8347: Cannot use a result of 'S.operator +(in int, S)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = x + default(S); // 3 + Diagnostic(ErrorCode.ERR_EscapeCall, "x + default(S)").WithArguments("S.operator +(in int, S)", "x").WithLocation(18, 13), + // (29,16): error CS9075: Cannot return a parameter by reference 'x' because it is scoped to the current method + // return x + default(S); // 4 + Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "x").WithArguments("x").WithLocation(29, 16), + // (29,16): error CS8347: Cannot use a result of 'S.operator +(in int, S)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return x + default(S); // 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "x + default(S)").WithArguments("S.operator +(in int, S)", "x").WithLocation(29, 16), + // (41,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 5 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(41, 16), + // (47,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 6 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(47, 16)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Nested() + { + var source = """ + class C + { + S M() + { + S s; + s = default(S) + 100 + 200; + return s; + } + } + + ref struct S + { + public static S operator+(S y, in int x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (6,13): error CS8347: Cannot use a result of 'S.operator +(S, in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = default(S) + 100 + 200; + Diagnostic(ErrorCode.ERR_EscapeCall, "default(S) + 100").WithArguments("S.operator +(S, in int)", "x").WithLocation(6, 13), + // (6,13): error CS8347: Cannot use a result of 'S.operator +(S, in int)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // s = default(S) + 100 + 200; + Diagnostic(ErrorCode.ERR_EscapeCall, "default(S) + 100 + 200").WithArguments("S.operator +(S, in int)", "y").WithLocation(6, 13), + // (6,26): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // s = default(S) + 100 + 200; + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "100").WithLocation(6, 26)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Scoped_Left() + { + var source = """ + ref struct R + { + private ref readonly int _i; + public R(in int i) { _i = ref i; } + public static R operator +(scoped R x, R y) => default; + } + class Program + { + static R F() + { + return new R(1) + new R(2); + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (11,16): error CS8347: Cannot use a result of 'R.operator +(scoped R, R)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // return new R(1) + new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1) + new R(2)").WithArguments("R.operator +(scoped R, R)", "y").WithLocation(11, 16), + // (11,27): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return new R(1) + new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(2)").WithArguments("R.R(in int)", "i").WithLocation(11, 27), + // (11,33): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R(1) + new R(2); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "2").WithLocation(11, 33)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Scoped_Right() + { + var source = """ + ref struct R + { + private ref readonly int _i; + public R(in int i) { _i = ref i; } + public static R operator +(R x, scoped R y) => default; + } + class Program + { + static R F() + { + return new R(1) + new R(2); + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (11,16): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return new R(1) + new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1)").WithArguments("R.R(in int)", "i").WithLocation(11, 16), + // (11,16): error CS8347: Cannot use a result of 'R.operator +(R, scoped R)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return new R(1) + new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1) + new R(2)").WithArguments("R.operator +(R, scoped R)", "x").WithLocation(11, 16), + // (11,22): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R(1) + new R(2); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(11, 22)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Scoped_Both() + { + var source = """ + ref struct R + { + private ref readonly int _i; + public R(in int i) { _i = ref i; } + public static R operator +(scoped R x, scoped R y) => default; + } + class Program + { + static R F() + { + return new R(1) + new R(2); + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Scoped_None() + { + var source = """ + ref struct R + { + private ref readonly int _i; + public R(in int i) { _i = ref i; } + public static R operator +(R x, R y) => default; + } + class Program + { + static R F() + { + return new R(1) + new R(2); + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (11,16): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return new R(1) + new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1)").WithArguments("R.R(in int)", "i").WithLocation(11, 16), + // (11,16): error CS8347: Cannot use a result of 'R.operator +(R, R)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return new R(1) + new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1) + new R(2)").WithArguments("R.operator +(R, R)", "x").WithLocation(11, 16), + // (11,22): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R(1) + new R(2); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(11, 22)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Compound() + { + var source = """ + public ref struct C + { + public static C operator +(C left, C right) => right; + public static C X(C left, C right) => right; + public C M(C c, scoped C c1) + { + c += c1; + c = c + c1; + c = X(c, c1); + return c; + } + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (7,9): error CS8347: Cannot use a result of 'C.operator +(C, C)' in this context because it may expose variables referenced by parameter 'right' outside of their declaration scope + // c += c1; + Diagnostic(ErrorCode.ERR_EscapeCall, "c += c1").WithArguments("C.operator +(C, C)", "right").WithLocation(7, 9), + // (7,14): error CS8352: Cannot use variable 'scoped C c1' in this context because it may expose referenced variables outside of their declaration scope + // c += c1; + Diagnostic(ErrorCode.ERR_EscapeVariable, "c1").WithArguments("scoped C c1").WithLocation(7, 14), + // (8,13): error CS8347: Cannot use a result of 'C.operator +(C, C)' in this context because it may expose variables referenced by parameter 'right' outside of their declaration scope + // c = c + c1; + Diagnostic(ErrorCode.ERR_EscapeCall, "c + c1").WithArguments("C.operator +(C, C)", "right").WithLocation(8, 13), + // (8,17): error CS8352: Cannot use variable 'scoped C c1' in this context because it may expose referenced variables outside of their declaration scope + // c = c + c1; + Diagnostic(ErrorCode.ERR_EscapeVariable, "c1").WithArguments("scoped C c1").WithLocation(8, 17), + // (9,13): error CS8347: Cannot use a result of 'C.X(C, C)' in this context because it may expose variables referenced by parameter 'right' outside of their declaration scope + // c = X(c, c1); + Diagnostic(ErrorCode.ERR_EscapeCall, "X(c, c1)").WithArguments("C.X(C, C)", "right").WithLocation(9, 13), + // (9,18): error CS8352: Cannot use variable 'scoped C c1' in this context because it may expose referenced variables outside of their declaration scope + // c = X(c, c1); + Diagnostic(ErrorCode.ERR_EscapeVariable, "c1").WithArguments("scoped C c1").WithLocation(9, 18)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Compound_Scoped_Left() + { + var source = """ + public ref struct C + { + public static C operator +(scoped C left, C right) => right; + public static C X(scoped C left, C right) => right; + public C M(C c, scoped C c1) + { + c += c1; + c = c + c1; + c = X(c, c1); + return c; + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (7,9): error CS8347: Cannot use a result of 'C.operator +(scoped C, C)' in this context because it may expose variables referenced by parameter 'right' outside of their declaration scope + // c += c1; + Diagnostic(ErrorCode.ERR_EscapeCall, "c += c1").WithArguments("C.operator +(scoped C, C)", "right").WithLocation(7, 9), + // (7,14): error CS8352: Cannot use variable 'scoped C c1' in this context because it may expose referenced variables outside of their declaration scope + // c += c1; + Diagnostic(ErrorCode.ERR_EscapeVariable, "c1").WithArguments("scoped C c1").WithLocation(7, 14), + // (8,13): error CS8347: Cannot use a result of 'C.operator +(scoped C, C)' in this context because it may expose variables referenced by parameter 'right' outside of their declaration scope + // c = c + c1; + Diagnostic(ErrorCode.ERR_EscapeCall, "c + c1").WithArguments("C.operator +(scoped C, C)", "right").WithLocation(8, 13), + // (8,17): error CS8352: Cannot use variable 'scoped C c1' in this context because it may expose referenced variables outside of their declaration scope + // c = c + c1; + Diagnostic(ErrorCode.ERR_EscapeVariable, "c1").WithArguments("scoped C c1").WithLocation(8, 17), + // (9,13): error CS8347: Cannot use a result of 'C.X(scoped C, C)' in this context because it may expose variables referenced by parameter 'right' outside of their declaration scope + // c = X(c, c1); + Diagnostic(ErrorCode.ERR_EscapeCall, "X(c, c1)").WithArguments("C.X(scoped C, C)", "right").WithLocation(9, 13), + // (9,18): error CS8352: Cannot use variable 'scoped C c1' in this context because it may expose referenced variables outside of their declaration scope + // c = X(c, c1); + Diagnostic(ErrorCode.ERR_EscapeVariable, "c1").WithArguments("scoped C c1").WithLocation(9, 18)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Compound_Scoped_Right() + { + var source = """ + public ref struct C + { + public static C operator +(C left, scoped C right) => left; + public static C X(C left, scoped C right) => left; + public C M(C c, scoped C c1) + { + c += c1; + c = c + c1; + c = X(c, c1); + return c; + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Compound_Scoped_Both() + { + var source = """ + public ref struct C + { + public static C operator +(scoped C left, scoped C right) => right; + public static C X(scoped C left, scoped C right) => right; + public C M(C c, scoped C c1) + { + c += c1; + c = c + c1; + c = X(c, c1); + return c; + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (3,66): error CS8352: Cannot use variable 'scoped C right' in this context because it may expose referenced variables outside of their declaration scope + // public static C operator +(scoped C left, scoped C right) => right; + Diagnostic(ErrorCode.ERR_EscapeVariable, "right").WithArguments("scoped C right").WithLocation(3, 66), + // (4,57): error CS8352: Cannot use variable 'scoped C right' in this context because it may expose referenced variables outside of their declaration scope + // public static C X(scoped C left, scoped C right) => right; + Diagnostic(ErrorCode.ERR_EscapeVariable, "right").WithArguments("scoped C right").WithLocation(4, 57)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Compound_ScopedTarget() + { + var source = """ + public ref struct C + { + public static C operator +(C left, C right) => right; + public static C X(C left, C right) => right; + public C M(scoped C c, C c1) + { + c += c1; + c = c + c1; + c = X(c, c1); + return c1; + } + } + """; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Compound_Scoped_Left_ScopedTarget() + { + var source = """ + public ref struct C + { + public static C operator +(scoped C left, C right) => right; + public static C X(scoped C left, C right) => right; + public C M(scoped C c, C c1) + { + c += c1; + c = c + c1; + c = X(c, c1); + return c1; + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Compound_Scoped_Right_ScopedTarget() + { + var source = """ + public ref struct C + { + public static C operator +(C left, scoped C right) => left; + public static C X(C left, scoped C right) => left; + public C M(scoped C c, C c1) + { + c += c1; + c = c + c1; + c = X(c, c1); + return c1; + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Compound_Scoped_Both_ScopedTarget() + { + var source = """ + public ref struct C + { + public static C operator +(scoped C left, scoped C right) => throw null; + public static C X(scoped C left, scoped C right) => throw null; + public C M(scoped C c, C c1) + { + c += c1; + c = c + c1; + c = X(c, c1); + return c1; + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedUnaryOperator_RefStruct() + { + var source = """ + class C + { + S M1() + { + S s; + s = +s; // 1 + return s; + } + + S M2() + { + return +new S(); // 2 + } + + S M3(in S x) + { + S s; + s = +x; // 3 + return s; + } + + S M4(in S x) + { + return +x; + } + + S M4s(scoped in S x) + { + return +x; // 4 + } + + S M5(in S x) + { + S s = +x; + return s; + } + + S M5s(scoped in S x) + { + S s = +x; + return s; // 5 + } + + S M6() + { + S s = +new S(); + return s; // 6 + } + + void M7(in S x) + { + scoped S s; + s = +x; + s = +new S(); + } + } + + ref struct S + { + public static S operator+(in S s) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (6,13): error CS8347: Cannot use a result of 'S.operator +(in S)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // s = +s; // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "+s").WithArguments("S.operator +(in S)", "s").WithLocation(6, 13), + // (6,14): error CS8168: Cannot return local 's' by reference because it is not a ref local + // s = +s; // 1 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s").WithArguments("s").WithLocation(6, 14), + // (12,16): error CS8347: Cannot use a result of 'S.operator +(in S)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // return +new S(); // 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "+new S()").WithArguments("S.operator +(in S)", "s").WithLocation(12, 16), + // (12,17): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return +new S(); // 2 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "new S()").WithLocation(12, 17), + // (18,13): error CS8347: Cannot use a result of 'S.operator +(in S)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // s = +x; // 3 + Diagnostic(ErrorCode.ERR_EscapeCall, "+x").WithArguments("S.operator +(in S)", "s").WithLocation(18, 13), + // (18,14): error CS9077: Cannot return a parameter by reference 'x' through a ref parameter; it can only be returned in a return statement + // s = +x; // 3 + Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "x").WithArguments("x").WithLocation(18, 14), + // (29,16): error CS8347: Cannot use a result of 'S.operator +(in S)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope + // return +x; // 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "+x").WithArguments("S.operator +(in S)", "s").WithLocation(29, 16), + // (29,17): error CS9075: Cannot return a parameter by reference 'x' because it is scoped to the current method + // return +x; // 4 + Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "x").WithArguments("x").WithLocation(29, 17), + // (41,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 5 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(41, 16), + // (47,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 6 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(47, 16)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedUnaryOperator_RefStruct_Scoped() + { + var source = """ + ref struct R + { + private ref readonly int _i; + public R(in int i) { _i = ref i; } + public static R operator !(scoped R r) => default; + } + class Program + { + static R F() + { + return !new R(0); + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedLogicalOperator_RefStruct() + { + var source = """ + class C + { + S M1(S s1, S s2) + { + S s = s1 && s2; + return s; // 1 + } + + S M2(S s1, S s2) + { + return s1 && s2; // 2 + } + + S M3(in S s1, in S s2) + { + S s = s1 && s2; + return s; + } + + S M4(scoped in S s1, in S s2) + { + S s = s1 && s2; + return s; // 3 + } + + S M5(in S s1, scoped in S s2) + { + S s = s1 && s2; + return s; // 4 + } + } + + ref struct S + { + public static bool operator true(in S s) => throw null; + public static bool operator false(in S s) => throw null; + public static S operator &(in S x, in S y) => throw null; + public static S operator |(in S x, in S y) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (6,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(6, 16), + // (11,16): error CS8166: Cannot return a parameter by reference 's1' because it is not a ref parameter + // return s1 && s2; // 2 + Diagnostic(ErrorCode.ERR_RefReturnParameter, "s1").WithArguments("s1").WithLocation(11, 16), + // (11,16): error CS8347: Cannot use a result of 'S.operator &(in S, in S)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return s1 && s2; // 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "s1 && s2").WithArguments("S.operator &(in S, in S)", "x").WithLocation(11, 16), + // (23,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 3 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(23, 16), + // (29,16): error CS8352: Cannot use variable 's' in this context because it may expose referenced variables outside of their declaration scope + // return s; // 4 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(29, 16)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedLogicalOperator_RefStruct_Scoped_Left() + { + var source = """ + ref struct R + { + private ref readonly int _i; + public R(in int i) { _i = ref i; } + public static bool operator true(R r) => true; + public static bool operator false(R r) => false; + public static R operator |(scoped R x, R y) => default; + } + class Program + { + static R F() + { + return new R(1) || new R(2); + } + + static R F2() + { + return new R(1) | new R(2); + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (13,16): error CS8347: Cannot use a result of 'R.operator |(scoped R, R)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // return new R(1) || new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1) || new R(2)").WithArguments("R.operator |(scoped R, R)", "y").WithLocation(13, 16), + // (13,28): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return new R(1) || new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(2)").WithArguments("R.R(in int)", "i").WithLocation(13, 28), + // (13,34): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R(1) || new R(2); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "2").WithLocation(13, 34), + // (18,16): error CS8347: Cannot use a result of 'R.operator |(scoped R, R)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // return new R(1) | new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1) | new R(2)").WithArguments("R.operator |(scoped R, R)", "y").WithLocation(18, 16), + // (18,27): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return new R(1) | new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(2)").WithArguments("R.R(in int)", "i").WithLocation(18, 27), + // (18,33): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R(1) | new R(2); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "2").WithLocation(18, 33)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedLogicalOperator_RefStruct_Scoped_Right() + { + var source = """ + ref struct R + { + private ref readonly int _i; + public R(in int i) { _i = ref i; } + public static bool operator true(R r) => true; + public static bool operator false(R r) => false; + public static R operator |(R x, scoped R y) => default; + } + class Program + { + static R F() + { + return new R(1) || new R(2); + } + + static R F2() + { + return new R(1) | new R(2); + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (13,16): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return new R(1) || new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1)").WithArguments("R.R(in int)", "i").WithLocation(13, 16), + // (13,16): error CS8347: Cannot use a result of 'R.operator |(R, scoped R)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return new R(1) || new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1) || new R(2)").WithArguments("R.operator |(R, scoped R)", "x").WithLocation(13, 16), + // (13,22): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R(1) || new R(2); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(13, 22), + // (18,16): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return new R(1) | new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1)").WithArguments("R.R(in int)", "i").WithLocation(18, 16), + // (18,16): error CS8347: Cannot use a result of 'R.operator |(R, scoped R)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return new R(1) | new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1) | new R(2)").WithArguments("R.operator |(R, scoped R)", "x").WithLocation(18, 16), + // (18,22): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R(1) | new R(2); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(18, 22)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedLogicalOperator_RefStruct_Scoped_Both() + { + var source = """ + ref struct R + { + private ref readonly int _i; + public R(in int i) { _i = ref i; } + public static bool operator true(R r) => true; + public static bool operator false(R r) => false; + public static R operator |(scoped R x, scoped R y) => default; + } + class Program + { + static R F() + { + return new R(1) || new R(2); + } + + static R F2() + { + return new R(1) | new R(2); + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedLogicalOperator_RefStruct_Scoped_None() + { + var source = """ + ref struct R + { + private ref readonly int _i; + public R(in int i) { _i = ref i; } + public static bool operator true(R r) => true; + public static bool operator false(R r) => false; + public static R operator |(R x, R y) => default; + } + class Program + { + static R F() + { + return new R(1) || new R(2); + } + + static R F2() + { + return new R(1) | new R(2); + } + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (13,16): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return new R(1) || new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1)").WithArguments("R.R(in int)", "i").WithLocation(13, 16), + // (13,16): error CS8347: Cannot use a result of 'R.operator |(R, R)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return new R(1) || new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1) || new R(2)").WithArguments("R.operator |(R, R)", "x").WithLocation(13, 16), + // (13,22): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R(1) || new R(2); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(13, 22), + // (18,16): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return new R(1) | new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1)").WithArguments("R.R(in int)", "i").WithLocation(18, 16), + // (18,16): error CS8347: Cannot use a result of 'R.operator |(R, R)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // return new R(1) | new R(2); + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1) | new R(2)").WithArguments("R.operator |(R, R)", "x").WithLocation(18, 16), + // (18,22): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return new R(1) | new R(2); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(18, 22)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72873")] + public void Utf8Addition() + { + var code = """ + using System; + ReadOnlySpan x = "Hello"u8 + " "u8 + "World!"u8; + Console.WriteLine(x.Length); + """; + CreateCompilation(code, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index caaf6545e972f..e7d8138d433f5 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -11990,7 +11990,7 @@ public static int Main() } [Fact] - public void CS0762ERR_PartialMethodToDelegate() + public void CS0762ERR_PartialMethodToDelegate_01() { var text = @" public delegate void TestDel(); @@ -12012,6 +12012,52 @@ public static int Main() new ErrorDescription[] { new ErrorDescription { Code = (int)ErrorCode.ERR_PartialMethodToDelegate, Line = 11, Column = 38 } }); } + [WorkItem("https://github.com/dotnet/roslyn/issues/72431")] + [Fact] + public void CS0762ERR_PartialMethodToDelegate_02() + { + var source = """ + delegate void D(); + partial class Program + { + static void Main() + { + M1(M2); + } + static void M1(D d) { } + static partial void M2(); + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (6,12): error CS0762: Cannot create delegate from method 'Program.M2()' because it is a partial method without an implementing declaration + // M1(M2); + Diagnostic(ErrorCode.ERR_PartialMethodToDelegate, "M2").WithArguments("Program.M2()").WithLocation(6, 12)); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72431")] + [Fact] + public void CS0762ERR_PartialMethodToDelegate_03() + { + var source = """ + delegate void D(); + partial class C + { + static void M1() + { + M2(C.M3); + } + static void M2(D d) { } + static partial void M3(); + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (6,12): error CS0762: Cannot create delegate from method 'C.M3()' because it is a partial method without an implementing declaration + // M2(C.M3); + Diagnostic(ErrorCode.ERR_PartialMethodToDelegate, "C.M3").WithArguments("C.M3()").WithLocation(6, 12)); + } + [Fact] public void CS0765ERR_PartialMethodInExpressionTree() { @@ -15304,10 +15350,10 @@ unsafe IEnumerator IteratorMeth2() { var expectedDiagnostics = new[] { - // (9,20): error CS9232: The '&' operator cannot be used on parameters or local variables in iterator methods. + // (9,20): error CS9239: The '&' operator cannot be used on parameters or local variables in iterator methods. // int *p = &i; Diagnostic(ErrorCode.ERR_AddressOfInIterator, "i").WithLocation(9, 20), - // (10,10): error CS9231: Cannot use 'yield return' in an 'unsafe' block + // (10,10): error CS9238: Cannot use 'yield return' in an 'unsafe' block // yield return *p; Diagnostic(ErrorCode.ERR_BadYieldInUnsafe, "yield").WithLocation(10, 10) }; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs index 6987f658c4acb..36d65147ec905 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs @@ -3275,19 +3275,19 @@ struct S3 }"; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics( - // (10,12): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (10,12): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S1() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "F1").WithLocation(10, 12), // (10,12): error CS0171: Field 'S1.F1' must be fully assigned before control is returned to the caller. Consider updating to language version '11.0' to auto-default the field. // public S1() { } Diagnostic(ErrorCode.ERR_UnassignedThisUnsupportedVersion, "S1").WithArguments("S1.F1", "11.0").WithLocation(10, 12), - // (16,5): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (16,5): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // S2(object? obj) { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S2").WithArguments("field", "F2").WithLocation(16, 5), // (16,5): error CS0171: Field 'S2.F2' must be fully assigned before control is returned to the caller. Consider updating to language version '11.0' to auto-default the field. // S2(object? obj) { } Diagnostic(ErrorCode.ERR_UnassignedThisUnsupportedVersion, "S2").WithArguments("S2.F2", "11.0").WithLocation(16, 5), - // (21,12): warning CS8618: Non-nullable field 'F3' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (21,12): warning CS8618: Non-nullable field 'F3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S3() { F3 = GetValue(); } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S3").WithArguments("field", "F3").WithLocation(21, 12), // (21,24): warning CS8601: Possible null reference assignment. @@ -3296,13 +3296,13 @@ struct S3 comp = CreateCompilation(source, parseOptions: TestOptions.Regular11); comp.VerifyDiagnostics( - // (10,12): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (10,12): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S1() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "F1").WithLocation(10, 12), - // (16,5): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (16,5): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // S2(object? obj) { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S2").WithArguments("field", "F2").WithLocation(16, 5), - // (21,12): warning CS8618: Non-nullable field 'F3' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (21,12): warning CS8618: Non-nullable field 'F3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S3() { F3 = GetValue(); } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S3").WithArguments("field", "F3").WithLocation(21, 12), // (21,24): warning CS8601: Possible null reference assignment. @@ -3721,9 +3721,15 @@ public Example() {} // (5,38): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Field = stackalloc int[512]; Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 38), + // (5,38): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope + // public ReadOnlySpan Field = stackalloc int[512]; + Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(5, 38), // (6,50): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(6, 50)); + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(6, 50), + // (6,50): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope + // public ReadOnlySpan Property { get; } = stackalloc int[512]; + Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(6, 50)); } [WorkItem(60568, "https://github.com/dotnet/roslyn/issues/60568")] @@ -3802,7 +3808,10 @@ public Example() {} Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "ReadOnlySpan").WithArguments("System.ReadOnlySpan").WithLocation(5, 12), // (5,50): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50)); + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50), + // (5,50): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope + // public ReadOnlySpan Property { get; } = stackalloc int[512]; + Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(5, 50)); } [WorkItem(60568, "https://github.com/dotnet/roslyn/issues/60568")] @@ -3829,7 +3838,10 @@ record struct Example() Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "ReadOnlySpan").WithArguments("System.ReadOnlySpan").WithLocation(5, 12), // (5,50): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50)); + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50), + // (5,50): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope + // public ReadOnlySpan Property { get; } = stackalloc int[512]; + Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(5, 50)); } [WorkItem(60568, "https://github.com/dotnet/roslyn/issues/60568")] @@ -3856,7 +3868,10 @@ class Example Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "ReadOnlySpan").WithArguments("System.ReadOnlySpan").WithLocation(5, 12), // (5,50): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50)); + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50), + // (5,50): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope + // public ReadOnlySpan Property { get; } = stackalloc int[512]; + Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(5, 50)); } [ConditionalFact(typeof(CoreClrOnly))] // For conversion from Span to ReadOnlySpan. @@ -4590,10 +4605,10 @@ public S(bool unused) }"; var verifier = CompileAndVerify(source, options: TestOptions.DebugDll.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings)); verifier.VerifyDiagnostics( - // (21,12): warning CS8618: Non-nullable field 'TField' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (21,12): warning CS8618: Non-nullable field 'TField' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "TField").WithLocation(21, 12), - // (21,12): warning CS8618: Non-nullable property 'AutoProp' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (21,12): warning CS8618: Non-nullable property 'AutoProp' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public S() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("property", "AutoProp").WithLocation(21, 12), // (23,20): warning CS8625: Cannot convert null literal to non-nullable reference type. @@ -4602,10 +4617,10 @@ public S(bool unused) // (24,18): warning CS8601: Possible null reference assignment. // TField = default; Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "default").WithLocation(24, 18), - // (29,12): warning CS8618: Non-nullable field 'TField' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (29,12): warning CS8618: Non-nullable field 'TField' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "TField").WithLocation(29, 12), - // (29,12): warning CS8618: Non-nullable property 'AutoProp' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (29,12): warning CS8618: Non-nullable property 'AutoProp' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public S(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("property", "AutoProp").WithLocation(29, 12), // (29,12): warning CS9022: Control is returned to caller before field 'S.TField' is explicitly assigned, causing a preceding implicit assignment of 'default'. @@ -4621,10 +4636,10 @@ public S(bool unused) verifier = CompileAndVerify(source, options: TestOptions.DebugDll); verifier.VerifyDiagnostics( - // (21,12): warning CS8618: Non-nullable field 'TField' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (21,12): warning CS8618: Non-nullable field 'TField' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "TField").WithLocation(21, 12), - // (21,12): warning CS8618: Non-nullable property 'AutoProp' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (21,12): warning CS8618: Non-nullable property 'AutoProp' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public S() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("property", "AutoProp").WithLocation(21, 12), // (23,20): warning CS8625: Cannot convert null literal to non-nullable reference type. @@ -4633,10 +4648,10 @@ public S(bool unused) // (24,18): warning CS8601: Possible null reference assignment. // TField = default; Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "default").WithLocation(24, 18), - // (29,12): warning CS8618: Non-nullable field 'TField' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (29,12): warning CS8618: Non-nullable field 'TField' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "TField").WithLocation(29, 12), - // (29,12): warning CS8618: Non-nullable property 'AutoProp' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (29,12): warning CS8618: Non-nullable property 'AutoProp' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public S(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("property", "AutoProp").WithLocation(29, 12)); verifyIL(); @@ -4698,7 +4713,7 @@ public S(bool unused) }"; var comp = CreateCompilation(new[] { source }, options: WithNullableEnable(), parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics( - // (4,12): warning CS8618: Non-nullable field 'Item' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (4,12): warning CS8618: Non-nullable field 'Item' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "Item").WithLocation(4, 12), // (4,12): error CS0171: Field 'S.Item' must be fully assigned before control is returned to the caller. Consider updating to language version '11.0' to auto-default the field. @@ -4708,7 +4723,7 @@ public S(bool unused) var verifier = CompileAndVerify(new[] { source }, options: WithNullableEnable(), parseOptions: TestOptions.Regular11); verifier.VerifyDiagnostics( - // (4,12): warning CS8618: Non-nullable field 'Item' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (4,12): warning CS8618: Non-nullable field 'Item' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "Item").WithLocation(4, 12)); verifier.VerifyIL("S..ctor", @" @@ -4738,7 +4753,7 @@ public S(bool unused) : this() }"; var verifier = CompileAndVerify(new[] { source }, options: WithNullableEnable(), parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); verifier.VerifyDiagnostics( - // (4,12): warning CS8618: Non-nullable field 'Item' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (4,12): warning CS8618: Non-nullable field 'Item' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "Item").WithLocation(4, 12) ); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs index e1c10b4bed11d..cdc7c38eca163 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs @@ -867,7 +867,7 @@ public S1(string s1, string s2) : this(s1) // (12,9): warning CS8602: Dereference of a possibly null reference. // Prop.ToString(); // 3 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(12, 9), - // (15,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (15,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public S1(object obj1, object obj2) : this() // 4 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("property", "Prop").WithLocation(15, 12)); @@ -879,7 +879,7 @@ public S1(string s1, string s2) : this(s1) // (12,9): warning CS8602: Dereference of a possibly null reference. // Prop.ToString(); // 3 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(12, 9), - // (15,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (15,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public S1(object obj1, object obj2) : this() // 4 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("property", "Prop").WithLocation(15, 12)); @@ -954,7 +954,7 @@ public S1(string s1, string s2) : this(s1) // (10,9): warning CS8602: Dereference of a possibly null reference. // field.ToString(); // 1 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "field").WithLocation(10, 9), - // (13,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (13,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S1(object obj1, object obj2) : this() // 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "field").WithLocation(13, 12)); } @@ -1051,7 +1051,7 @@ public S1(string s) // 1, 2 "; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics( - // (13,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (13,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S1(string s) // 1, 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "field").WithLocation(13, 12), // (13,12): error CS0171: Field 'S1.field' must be fully assigned before control is returned to the caller. Consider updating to language version '11.0' to auto-default the field. @@ -1064,7 +1064,7 @@ public S1(string s) // 1, 2 var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular11); verifier.VerifyDiagnostics( - // (13,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (13,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S1(string s) // 1, 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "field").WithLocation(13, 12) ); @@ -1781,10 +1781,10 @@ internal S(string s) }"; var comp = CreateCompilation(new[] { source }, options: WithNullableEnable(), parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( - // (6,14): warning CS8618: Non-nullable property 'P' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (6,14): warning CS8618: Non-nullable property 'P' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // internal S(string s) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("property", "P").WithLocation(6, 14), - // (6,14): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (6,14): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // internal S(string s) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "F").WithLocation(6, 14), // (6,14): error CS0843: Auto-implemented property 'S.P' must be fully assigned before control is returned to the caller. Consider updating to language version '11.0' to auto-default the property. @@ -1796,10 +1796,10 @@ internal S(string s) var verifier = CompileAndVerify(new[] { source }, options: WithNullableEnable(), parseOptions: TestOptions.Regular11); verifier.VerifyDiagnostics( - // (6,14): warning CS8618: Non-nullable property 'P' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (6,14): warning CS8618: Non-nullable property 'P' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // internal S(string s) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("property", "P").WithLocation(6, 14), - // (6,14): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (6,14): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // internal S(string s) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "F").WithLocation(6, 14)); verifier.VerifyIL("S..ctor", @" @@ -1984,13 +1984,13 @@ public interface I // (4,19): error CS0525: Interfaces cannot contain instance fields // public object F1; // 1 Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "F1").WithLocation(4, 19), - // (5,26): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (5,26): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public static object F2; // 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "F2").WithArguments("field", "F2").WithLocation(5, 26), - // (6,26): warning CS8618: Non-nullable property 'F3' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (6,26): warning CS8618: Non-nullable property 'F3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public static object F3 { get; set; } // 3 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "F3").WithArguments("property", "F3").WithLocation(6, 26), - // (7,39): warning CS8618: Non-nullable event 'E1' must contain a non-null value when exiting constructor. Consider declaring the event as nullable. + // (7,39): warning CS8618: Non-nullable event 'E1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the event as nullable. // public static event System.Action E1; // 4, 5 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "E1").WithArguments("event", "E1").WithLocation(7, 39), // (7,39): warning CS0067: The event 'I.E1' is never used @@ -2030,13 +2030,13 @@ static I() // 2, 3, 4 // (6,39): warning CS0067: The event 'I.E1' is never used // public static event System.Action E1; // 1 Diagnostic(ErrorCode.WRN_UnreferencedEvent, "E1").WithArguments("I.E1").WithLocation(6, 39), - // (16,12): warning CS8618: Non-nullable property 'F2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (16,12): warning CS8618: Non-nullable property 'F2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // static I() // 2, 3, 4 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "I").WithArguments("property", "F2").WithLocation(16, 12), - // (16,12): warning CS8618: Non-nullable event 'E1' must contain a non-null value when exiting constructor. Consider declaring the event as nullable. + // (16,12): warning CS8618: Non-nullable event 'E1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the event as nullable. // static I() // 2, 3, 4 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "I").WithArguments("event", "E1").WithLocation(16, 12), - // (16,12): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (16,12): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // static I() // 2, 3, 4 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "I").WithArguments("field", "F1").WithLocation(16, 12) ); @@ -2077,10 +2077,10 @@ void L(object o) // Null state does not flow out of local functions https://github.com/dotnet/roslyn/issues/45770 var comp = CreateCompilation(new[] { source }, options: WithNullableEnable(), parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( - // (6,5): warning CS8618: Non-nullable field 'G' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (6,5): warning CS8618: Non-nullable field 'G' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // C() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("field", "G").WithLocation(6, 5), - // (6,5): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (6,5): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // C() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("field", "F").WithLocation(6, 5)); } @@ -2188,7 +2188,7 @@ public C3() }"; var comp = CreateCompilation(new[] { source, DoesNotReturnIfAttributeDefinition }, options: WithNullableEnable()); comp.VerifyDiagnostics( - // (9,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (9,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C1() // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C1").WithArguments("property", "Prop").WithLocation(9, 12)); } @@ -2336,7 +2336,7 @@ public Derived() "; var comp = CreateCompilation(source, options: WithNullableEnable()); comp.VerifyDiagnostics( - // (4,19): warning CS8618: Non-nullable property 'BaseProp' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (4,19): warning CS8618: Non-nullable property 'BaseProp' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public string BaseProp { get; set; } // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "BaseProp").WithArguments("property", "BaseProp").WithLocation(4, 19), // (14,9): warning CS8602: Dereference of a possibly null reference. @@ -2558,21 +2558,21 @@ public partial class Class1 comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[0], filterSpanWithinTree: null, includeEarlierStages: true) .Verify( - // (4,35): warning CS8618: Non-nullable field 'Value1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (4,35): warning CS8618: Non-nullable field 'Value1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public static readonly string Value1; // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Value1").WithArguments("field", "Value1").WithLocation(4, 35)); comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[1], filterSpanWithinTree: null, includeEarlierStages: true) .Verify( - // (4,35): warning CS8618: Non-nullable field 'Value2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (4,35): warning CS8618: Non-nullable field 'Value2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public static readonly string Value2; // 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Value2").WithArguments("field", "Value2").WithLocation(4, 35)); comp.VerifyDiagnostics( - // (4,35): warning CS8618: Non-nullable field 'Value1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (4,35): warning CS8618: Non-nullable field 'Value1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public static readonly string Value1; // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Value1").WithArguments("field", "Value1").WithLocation(4, 35), - // (4,35): warning CS8618: Non-nullable field 'Value2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (4,35): warning CS8618: Non-nullable field 'Value2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public static readonly string Value2; // 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Value2").WithArguments("field", "Value2").WithLocation(4, 35)); } @@ -2599,12 +2599,12 @@ public partial class Class1 comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[1], filterSpanWithinTree: null, includeEarlierStages: true) .Verify( - // (4,35): warning CS8618: Non-nullable field 'Value2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (4,35): warning CS8618: Non-nullable field 'Value2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public static readonly string Value2; // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Value2").WithArguments("field", "Value2").WithLocation(4, 35)); comp.VerifyDiagnostics( - // (4,35): warning CS8618: Non-nullable field 'Value2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (4,35): warning CS8618: Non-nullable field 'Value2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public static readonly string Value2; // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Value2").WithArguments("field", "Value2").WithLocation(4, 35)); } @@ -2741,7 +2741,7 @@ public C() { } }"; var comp = CreateCompilation(source, options: WithNullableEnable()); comp.VerifyDiagnostics( - // (4,12): warning CS8618: Non-nullable property 'S' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (4,12): warning CS8618: Non-nullable property 'S' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "S").WithLocation(4, 12)); @@ -2762,7 +2762,7 @@ public class C }"; var comp = CreateCompilation(source, options: WithNullableEnable()); comp.VerifyDiagnostics( - // (4,19): warning CS8618: Non-nullable property 'S' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (4,19): warning CS8618: Non-nullable property 'S' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public string S { get; } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("property", "S").WithLocation(4, 19)); @@ -2798,7 +2798,7 @@ static void Main() var diagnostics = comp.GetDiagnostics(); diagnostics.Verify( - // (8,14): warning CS8618: Non-nullable field 'f' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (8,14): warning CS8618: Non-nullable field 'f' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public B f; Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "f").WithArguments("field", "f").WithLocation(8, 14).WithWarningAsError(warnAsError)); @@ -2824,10 +2824,10 @@ public Rec(Rec other) // 2 var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); comp.VerifyEmitDiagnostics( - // 0.cs(6,19): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // 0.cs(6,19): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public string Prop { get; init; } // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Prop").WithArguments("property", "Prop").WithLocation(6, 19), - // 0.cs(7,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // 0.cs(7,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Rec(Rec other) // 2 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Rec").WithArguments("property", "Prop").WithLocation(7, 12)); } @@ -2847,7 +2847,7 @@ public Rec(Rec other) // 1 var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); comp.VerifyEmitDiagnostics( - // 0.cs(4,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // 0.cs(4,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Rec(Rec other) // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Rec").WithArguments("property", "Prop").WithLocation(4, 12)); } @@ -2922,7 +2922,7 @@ public void M() var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition, RequiredMemberAttribute, SetsRequiredMembersAttribute, CompilerFeatureRequiredAttribute }); comp.VerifyEmitDiagnostics( - // 0.cs(9,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // 0.cs(9,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Rec(Rec other) { } // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Rec").WithArguments("property", "Prop").WithLocation(9, 12)); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs index 2c2a8d1f423e4..b34a35cf0c104 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs @@ -437,7 +437,7 @@ public System.Collections.Generic.IEnumerable M() var expectedDiagnostics = new[] { - // (8,23): error CS9232: The '&' operator cannot be used on parameters or local variables in iterator methods. + // (8,23): error CS9239: The '&' operator cannot be used on parameters or local variables in iterator methods. // int *p = &x; Diagnostic(ErrorCode.ERR_AddressOfInIterator, "x").WithLocation(8, 23) }; @@ -484,7 +484,7 @@ public System.Collections.Generic.IEnumerable M(int x) var expectedDiagnostics = new[] { - // (7,23): error CS9232: The '&' operator cannot be used on parameters or local variables in iterator methods. + // (7,23): error CS9239: The '&' operator cannot be used on parameters or local variables in iterator methods. // int *p = &x; Diagnostic(ErrorCode.ERR_AddressOfInIterator, "x").WithLocation(7, 23) }; @@ -557,7 +557,7 @@ static System.Collections.Generic.IEnumerable F() } """; CreateCompilation(code, options: TestOptions.UnsafeReleaseDll).VerifyDiagnostics( - // (12,23): error CS9232: The '&' operator cannot be used on parameters or local variables in iterator methods. + // (12,23): error CS9239: The '&' operator cannot be used on parameters or local variables in iterator methods. // int* p = &s.F; Diagnostic(ErrorCode.ERR_AddressOfInIterator, "s.F").WithLocation(12, 23)); } @@ -668,7 +668,7 @@ public System.Collections.Generic.IEnumerable M() var expectedDiagnostics = new[] { - // (7,13): error CS9231: Cannot use 'yield return' in an 'unsafe' block + // (7,13): error CS9238: Cannot use 'yield return' in an 'unsafe' block // yield return 1; Diagnostic(ErrorCode.ERR_BadYieldInUnsafe, "yield").WithLocation(7, 13) }; @@ -740,13 +740,13 @@ public System.Collections.Generic.IEnumerable M() var expectedDiagnostics = new[] { - // (8,23): error CS9232: The '&' operator cannot be used on parameters or local variables in iterator methods. + // (8,23): error CS9239: The '&' operator cannot be used on parameters or local variables in iterator methods. // int *p = &x; Diagnostic(ErrorCode.ERR_AddressOfInIterator, "x").WithLocation(8, 23), - // (14,23): error CS9232: The '&' operator cannot be used on parameters or local variables in iterator methods. + // (14,23): error CS9239: The '&' operator cannot be used on parameters or local variables in iterator methods. // int *p = &x; Diagnostic(ErrorCode.ERR_AddressOfInIterator, "x").WithLocation(14, 23), - // (20,23): error CS9232: The '&' operator cannot be used on parameters or local variables in iterator methods. + // (20,23): error CS9239: The '&' operator cannot be used on parameters or local variables in iterator methods. // int *p = &x; Diagnostic(ErrorCode.ERR_AddressOfInIterator, "x").WithLocation(20, 23) }; @@ -2357,7 +2357,7 @@ public void UnsafeContext_Lambda_Iterator_Body_Unsafe() // (5,9): error CS1621: The yield statement cannot be used inside an anonymous method or lambda expression // yield return sizeof(nint); Diagnostic(ErrorCode.ERR_YieldInAnonMeth, "yield").WithLocation(5, 9), - // (5,9): error CS9231: Cannot use 'yield return' in an 'unsafe' block + // (5,9): error CS9238: Cannot use 'yield return' in an 'unsafe' block // yield return sizeof(nint); Diagnostic(ErrorCode.ERR_BadYieldInUnsafe, "yield").WithLocation(5, 9) ]; diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs index 47dfa645cde10..5bc7afbc0cc23 100644 --- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs @@ -4080,5 +4080,101 @@ internal static void VerifyGeneratorExceptionDiagnostic( var expectedDetails = $"System.{typeName}: {message}{Environment.NewLine} "; Assert.StartsWith(expectedDetails, diagnostic.Arguments[3] as string); } + + [Fact] + public void GetInterceptsLocationSpecifier_01() + { + var generator = new IncrementalGeneratorWrapper(new InterceptorGenerator1()); + + var parseOptions = TestOptions.RegularPreview.WithFeature("InterceptorsPreviewNamespaces", "global"); + + var source1 = (""" + public class Program + { + public static void Main() + { + var program = new Program(); + program.M(1); + } + + public void M(int param) => throw null!; + } + + namespace System.Runtime.CompilerServices + { + public class InterceptsLocationAttribute : Attribute { public InterceptsLocationAttribute(int version, string data) { } } + } + """, PlatformInformation.IsWindows ? @"C:\project\src\Program.cs" : "/project/src/Program.cs"); + + Compilation compilation = CreateCompilation([source1], options: TestOptions.DebugExe, parseOptions: parseOptions); + + GeneratorDriver driver = CSharpGeneratorDriver.Create([generator], parseOptions: parseOptions, driverOptions: new GeneratorDriverOptions() { BaseDirectory = PlatformInformation.IsWindows ? @"C:\project\obj\" : "/project/obj" }); + verify(ref driver, compilation); + + void verify(ref GeneratorDriver driver, Compilation compilation) + { + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generatorDiagnostics); + outputCompilation.VerifyDiagnostics(); + CompileAndVerify(outputCompilation, expectedOutput: "1"); + generatorDiagnostics.Verify(); + } + } + + [Generator(LanguageNames.CSharp)] + private class InterceptorGenerator1 : IIncrementalGenerator + { +#pragma warning disable RSEXPERIMENTAL002 // test + record InterceptorInfo(InterceptableLocation locationSpecifier, object data); + + private static bool IsInterceptableCall(SyntaxNode node, CancellationToken token) => node is InvocationExpressionSyntax; + + private static object GetData(GeneratorSyntaxContext context) => 1; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var interceptorInfos = context.SyntaxProvider.CreateSyntaxProvider( + predicate: IsInterceptableCall, + transform: (GeneratorSyntaxContext context, CancellationToken token) => + { + var model = context.SemanticModel; + var locationSpecifier = model.GetInterceptableLocation((InvocationExpressionSyntax)context.Node, token); + if (locationSpecifier is null) + { + return null; // generator wants to intercept call, but host thinks call is not interceptable. bug. + } + + // generator is careful to propagate only equatable data (i.e., not syntax nodes or symbols). + return new InterceptorInfo(locationSpecifier, GetData(context)); + }) + .Where(info => info != null) + .Collect(); + + context.RegisterSourceOutput(interceptorInfos, (context, interceptorInfos) => + { + var builder = new StringBuilder(); + builder.AppendLine("using System.Runtime.CompilerServices;"); + builder.AppendLine("using System;"); + builder.AppendLine("public static class Interceptors"); + builder.AppendLine("{"); + // builder boilerplate.. + foreach (var interceptorInfo in interceptorInfos) + { + var (locationSpecifier, data) = interceptorInfo!; + builder.AppendLine($$""" + // {{locationSpecifier.GetDisplayLocation()}} + [InterceptsLocation({{locationSpecifier.Version}}, "{{locationSpecifier.Data}}")] + public static void Interceptor(this Program program, int param) + { + Console.Write(1); + } + """); + } + // builder boilerplate.. + builder.AppendLine("}"); + + context.AddSource("MyInterceptors.cs", builder.ToString()); + }); + } + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs index 882913c850da3..2e233219a3fc0 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs @@ -225,7 +225,7 @@ public void M(dynamic d) "; var semanticInfo = GetSemanticInfoForTest(sourceCode1); - Assert.Equal("C", semanticInfo.Type.ToTestDisplayString()); + Assert.True(semanticInfo.Type.IsDynamic()); Assert.Equal("C C.Create(System.Int32 arg)", semanticInfo.Symbol.ToTestDisplayString()); Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); Assert.Equal(0, semanticInfo.CandidateSymbols.Length); @@ -548,8 +548,8 @@ public int this[int a] "; var semanticInfo = GetSemanticInfoForTest(sourceCode); - Assert.False(semanticInfo.Type.IsDynamic()); - Assert.False(semanticInfo.ConvertedType.IsDynamic()); + Assert.True(semanticInfo.Type.IsDynamic()); + Assert.True(semanticInfo.ConvertedType.IsDynamic()); Assert.Equal(ConversionKind.Identity, semanticInfo.ImplicitConversion.Kind); Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/CorLibrary/CorTypes.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/CorLibrary/CorTypes.cs index f43eeeb797768..ec6de998246ec 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/CorLibrary/CorTypes.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/CorLibrary/CorTypes.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -269,5 +270,801 @@ internal class ArrayContract : Array // internal class ArrayContract : Array Diagnostic(ErrorCode.ERR_DeriveFromEnumOrValueType, "Array").WithArguments("System.ArrayContract", "System.Array")); } + + [Fact] + public void System_Type__WellKnownVsSpecial_01() + { + var source = @" +class Program +{ + static void Main() + { + var x = typeof(Program); + System.Console.WriteLine(x); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + comp.MakeMemberMissing(WellKnownMember.System_Type__GetTypeFromHandle); + + Assert.False(comp.GetSpecialType(InternalSpecialType.System_Type).IsErrorType()); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = comp.GetSemanticModel(tree); + + Assert.Equal(InternalSpecialType.System_Type, model.GetTypeInfo(node).Type.GetSymbol().ExtendedSpecialType); + + CompileAndVerify(comp, expectedOutput: "Program"); + + comp = CreateCompilation(source, options: TestOptions.DebugExe); + comp.MakeMemberMissing(SpecialMember.System_Type__GetTypeFromHandle); + comp.VerifyEmitDiagnostics( + // (6,17): error CS0656: Missing compiler required member 'System.Type.GetTypeFromHandle' + // var x = typeof(Program); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "typeof(Program)").WithArguments("System.Type", "GetTypeFromHandle").WithLocation(6, 17) + ); + } + + [Fact] + public void System_Type__WellKnownVsSpecial_02() + { + var corLib_v1 = @" +namespace System +{ + public class Object + {} + + public class Void + {} + + public class ValueType + {} + + public struct RuntimeTypeHandle + {} +} +"; + var corLib_v1_Comp = CreateEmptyCompilation(corLib_v1, assemblyName: "corLib"); + + var typeLib_v1 = @" +namespace System +{ + public class Type + { + public static Type GetTypeFromHandle(RuntimeTypeHandle handle) => null; + } +} +"; + + var typeLib_v1_Comp = CreateEmptyCompilation(typeLib_v1, references: [corLib_v1_Comp.ToMetadataReference()], assemblyName: "typeLib"); + + var source1 = @" +#nullable disable + +public class Test +{ + public static System.Type TypeOf() => typeof(Test); +} +"; + var comp1 = CreateEmptyCompilation( + source1, references: [corLib_v1_Comp.ToMetadataReference(), typeLib_v1_Comp.ToMetadataReference()], + parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute()); + + Assert.True(comp1.GetSpecialType(InternalSpecialType.System_Type).IsErrorType()); + comp1.MakeMemberMissing(SpecialMember.System_Type__GetTypeFromHandle); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var model = comp1.GetSemanticModel(tree); + + Assert.Equal((ExtendedSpecialType)0, model.GetTypeInfo(node).Type.GetSymbol().ExtendedSpecialType); + + var comp1Ref = comp1.EmitToImageReference(); + + var corLib_v2 = @" +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Object))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(void))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ValueType))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeTypeHandle))] +"; + var corLib_v2_Comp = CreateCompilation(corLib_v2, assemblyName: "corLib"); + + var typeLib_v2 = @" +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Type))] +"; + + var typeLib_v2_Comp = CreateCompilation(typeLib_v2, assemblyName: "typeLib"); + + var source2 = @" +class Program +{ + static void Main() + { + System.Console.WriteLine(Test.TypeOf()); + } +} +"; + + var comp = CreateCompilation(source2, references: [corLib_v2_Comp.ToMetadataReference(), typeLib_v2_Comp.ToMetadataReference(), comp1Ref], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: "Test"); + + comp1 = CreateEmptyCompilation( + source1, references: [corLib_v1_Comp.ToMetadataReference(), typeLib_v1_Comp.ToMetadataReference()], + parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute()); + + comp1.MakeMemberMissing(WellKnownMember.System_Type__GetTypeFromHandle); + comp1.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), + // (6,43): error CS0656: Missing compiler required member 'System.Type.GetTypeFromHandle' + // public static System.Type TypeOf() => typeof(Test); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "typeof(Test)").WithArguments("System.Type", "GetTypeFromHandle").WithLocation(6, 43) + ); + } + + [Fact] + public void System_Type__WellKnownVsSpecial_03() + { + var source = @" +record R +{ + public static System.Type TypeOf() => new R().EqualityContract; +} + +class Program +{ + static void Main() + { + System.Console.WriteLine(R.TypeOf()); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + comp.MakeMemberMissing(WellKnownMember.System_Type__GetTypeFromHandle); + + Assert.False(comp.GetSpecialType(InternalSpecialType.System_Type).IsErrorType()); + + CompileAndVerify(comp, expectedOutput: "R"); + + comp = CreateCompilation(source, options: TestOptions.DebugExe); + comp.MakeMemberMissing(SpecialMember.System_Type__GetTypeFromHandle); + comp.VerifyEmitDiagnostics( + // (2,1): error CS0656: Missing compiler required member 'System.Type.GetTypeFromHandle' + // record R + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"record R +{ + public static System.Type TypeOf() => new R().EqualityContract; +}").WithArguments("System.Type", "GetTypeFromHandle").WithLocation(2, 1) + ); + } + + [Fact] + public void System_Type__WellKnownVsSpecial_04() + { + var corLib_v1 = @" +namespace System +{ + public class Object + { + public virtual string ToString() => null; + public virtual int GetHashCode() => 0; + public virtual bool Equals(object obj) => false; + } + + public class Void + {} + + public class ValueType + {} + + public struct RuntimeTypeHandle + {} + + public struct Byte + {} + + public struct Int32 + {} + + public struct Boolean + {} + + public class String + {} + + public interface IEquatable + { + bool Equals(T other); + } + + public class Attribute + {} + + public enum AttributeTargets + { + } + public class AttributeUsageAttribute + { + public AttributeUsageAttribute(AttributeTargets validOn){} + public bool AllowMultiple => false; + public bool Inherited => false; + } + + public class Exception + {} + + namespace Text + { + public sealed class StringBuilder + {} + + } +} +"; + var corLib_v1_Comp = CreateEmptyCompilation(corLib_v1, assemblyName: "corLib"); + + var typeLib_v1 = @" +namespace System +{ + public class Type + { + public static Type GetTypeFromHandle(RuntimeTypeHandle handle) => null; + } +} +"; + + var typeLib_v1_Comp = CreateEmptyCompilation(typeLib_v1, references: [corLib_v1_Comp.ToMetadataReference()], assemblyName: "typeLib"); + + var source1 = @" +#nullable disable + +sealed public record R +{ + public static System.Type TypeOf() => new R().EqualityContract; + public override string ToString() => null; + public override int GetHashCode() => 0; + public bool Equals(R obj) => false; +} +"; + var comp1 = CreateEmptyCompilation( + source1, references: [corLib_v1_Comp.ToMetadataReference(), typeLib_v1_Comp.ToMetadataReference()], + parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute()); + + Assert.True(comp1.GetSpecialType(InternalSpecialType.System_Type).IsErrorType()); + comp1.MakeMemberMissing(SpecialMember.System_Type__GetTypeFromHandle); + + var comp1Ref = comp1.EmitToImageReference(); + + var corLib_v2 = @" +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Object))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(void))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ValueType))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeTypeHandle))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Byte))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Int32))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Boolean))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.String))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Attribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.AttributeTargets))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.AttributeUsageAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Exception))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IEquatable<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Text.StringBuilder))] +"; + var corLib_v2_Comp = CreateCompilation(corLib_v2, assemblyName: "corLib"); + + var typeLib_v2 = @" +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Type))] +"; + + var typeLib_v2_Comp = CreateCompilation(typeLib_v2, assemblyName: "typeLib"); + + var source2 = @" +class Program +{ + static void Main() + { + System.Console.WriteLine(R.TypeOf()); + } +} +"; + + var comp = CreateCompilation(source2, references: [corLib_v2_Comp.ToMetadataReference(), typeLib_v2_Comp.ToMetadataReference(), comp1Ref], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: "R"); + + comp1 = CreateEmptyCompilation( + source1, references: [corLib_v1_Comp.ToMetadataReference(), typeLib_v1_Comp.ToMetadataReference()], + parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute()); + + comp1.MakeMemberMissing(WellKnownMember.System_Type__GetTypeFromHandle); + comp1.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), + // (4,1): error CS0656: Missing compiler required member 'System.Type.GetTypeFromHandle' + // sealed public record R + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"sealed public record R +{ + public static System.Type TypeOf() => new R().EqualityContract; + public override string ToString() => null; + public override int GetHashCode() => 0; + public bool Equals(R obj) => false; +}").WithArguments("System.Type", "GetTypeFromHandle").WithLocation(4, 1) + ); + } + + [Fact] + public void CreateDelegate__MethodInfoVsDelegate_01() + { + var source = @" +class Program +{ + static void Main() + { + System.Linq.Expressions.Expression> x = () => C1.M1; + System.Console.WriteLine(x); + } +} + +class C1 +{ + public static void M1() {} +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Mscorlib40AndSystemCore, options: TestOptions.DebugExe); + comp.MakeMemberMissing(WellKnownMember.System_Reflection_MethodInfo__CreateDelegate); + comp.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle2); + comp.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle); + comp.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2); + + CompileAndVerify(comp, expectedOutput: "() => Convert(CreateDelegate(System.Action, null, Void M1())" + + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Action" : "") + + ")"); + + comp = CreateCompilation(source, targetFramework: TargetFramework.Mscorlib40AndSystemCore, options: TestOptions.DebugExe); + comp.MakeMemberMissing(SpecialMember.System_Delegate__CreateDelegate); + comp.VerifyEmitDiagnostics( + // (6,82): error CS0656: Missing compiler required member 'System.Delegate.CreateDelegate' + // System.Linq.Expressions.Expression> x = () => C1.M1; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C1.M1").WithArguments("System.Delegate", "CreateDelegate").WithLocation(6, 82) + ); + + comp = CreateCompilation(source, targetFramework: TargetFramework.Mscorlib40AndSystemCore, options: TestOptions.DebugExe); + comp.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle); + comp.VerifyEmitDiagnostics( + // (6,82): error CS0656: Missing compiler required member 'System.Reflection.MethodBase.GetMethodFromHandle' + // System.Linq.Expressions.Expression> x = () => C1.M1; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C1.M1").WithArguments("System.Reflection.MethodBase", "GetMethodFromHandle").WithLocation(6, 82) + ); + } + + [Fact] + public void CreateDelegate__MethodInfoVsDelegate_02() + { + var source = @" +class Program +{ + static void Main() + { + System.Linq.Expressions.Expression> x = () => C1.M1; + System.Console.WriteLine(x); + } +} + +class C1 +{ + public static void M1() {} +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + comp.MakeMemberMissing(SpecialMember.System_Delegate__CreateDelegate); + comp.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle); + comp.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2); + + CompileAndVerify( + comp, expectedOutput: "() => Convert(Void M1().CreateDelegate(System.Action, null)" + + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Action" : "") + + ")"); + + comp = CreateCompilation(source, options: TestOptions.DebugExe); + comp.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle2); + comp.VerifyEmitDiagnostics( + // (6,82): error CS0656: Missing compiler required member 'System.Reflection.MethodBase.GetMethodFromHandle' + // System.Linq.Expressions.Expression> x = () => C1.M1; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C1.M1").WithArguments("System.Reflection.MethodBase", "GetMethodFromHandle").WithLocation(6, 82) + ); + } + + [Fact] + public void GetMethodFromHandle_WellKnown_01() + { + var corLib_v1 = @" +namespace System +{ + public class Object + {} + + public class Void + {} + + public class ValueType + {} + + public struct RuntimeTypeHandle + {} + + public struct RuntimeMethodHandle + {} + + public struct Int32 + {} + + public abstract class Delegate + {} + + public abstract class MulticastDelegate : Delegate + {} + + public delegate void Action(); + + public delegate TResult Func(); + + public struct Nullable + {} +} + +namespace System.Collections.Generic +{ + public interface IEnumerable + {} +} +"; + var corLib_v1_Comp = CreateEmptyCompilation(corLib_v1, assemblyName: "corLib"); + + var typeLib_v1 = @" +namespace System +{ + public class Type + { + public static Type GetTypeFromHandle(RuntimeTypeHandle handle) => null; + } +} +namespace System.Reflection +{ + public abstract partial class MethodBase + { + public static MethodBase GetMethodFromHandle(RuntimeMethodHandle handle) => null; + } + + public abstract partial class MethodInfo : MethodBase + { + public virtual Delegate CreateDelegate(Type delegateType) => null; + public virtual Delegate CreateDelegate(Type delegateType, object target) => null; + } +} + +namespace System.Linq.Expressions +{ + public abstract class Expression + { + public static ConstantExpression Constant (object value) => null; + public static ConstantExpression Constant (object value, Type type) => null; + + public static MethodCallExpression Call (Expression instance, System.Reflection.MethodInfo method, Expression[] arguments) => null; + + public static UnaryExpression Convert (Expression expression, Type type) => null; + public static UnaryExpression Convert (Expression expression, Type type, System.Reflection.MethodInfo method) => null; + + public static Expression Lambda (Expression body, ParameterExpression[] parameters) => null; + } + + public abstract class LambdaExpression : Expression + {} + + public abstract class Expression : LambdaExpression + {} + + public class ConstantExpression : Expression + {} + + public class ParameterExpression : Expression + {} + + public class MethodCallExpression : Expression + {} + + public sealed class UnaryExpression : Expression + {} +} +"; + + var typeLib_v1_Comp = CreateEmptyCompilation(typeLib_v1, references: [corLib_v1_Comp.ToMetadataReference()], assemblyName: "typeLib"); + + typeLib_v1_Comp.VerifyDiagnostics(); + + var source1 = @" +#nullable disable + +public class Test +{ + public static System.Linq.Expressions.Expression> Expression() + { + return () => C1.M1; + } +} + +class C1 +{ + public static void M1() {} +} +"; + var comp1 = CreateEmptyCompilation( + source1, references: [corLib_v1_Comp.ToMetadataReference(), typeLib_v1_Comp.ToMetadataReference()], + parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute()); + + comp1.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle); + comp1.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle2); + comp1.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2); + + var comp1Ref = comp1.EmitToImageReference(); + + var corLib_v2 = @" +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Object))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(void))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ValueType))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeTypeHandle))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeMethodHandle))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Int32))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Action))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Func<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Nullable<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.IEnumerable<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Delegate))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.MulticastDelegate))] +"; + var corLib_v2_Comp = CreateCompilation(corLib_v2, assemblyName: "corLib"); + + var typeLib_v2 = @" +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Type))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.MethodBase))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.MethodInfo))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.Expression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.LambdaExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.Expression<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.ConstantExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.ParameterExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.MethodCallExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.UnaryExpression))] +"; + + var typeLib_v2_Comp = CreateCompilation(typeLib_v2, assemblyName: "typeLib"); + + var source2 = @" +class Program +{ + static void Main() + { + System.Console.WriteLine(Test.Expression()); + } +} +"; + + var comp = CreateCompilation(source2, references: [corLib_v2_Comp.ToMetadataReference(), typeLib_v2_Comp.ToMetadataReference(), comp1Ref], options: TestOptions.DebugExe); + + CompileAndVerify(comp, expectedOutput: "() => Convert(Void M1().CreateDelegate(System.Action, null)" + + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Action" : "") + + ")"); + + comp1 = CreateEmptyCompilation( + source1, references: [corLib_v1_Comp.ToMetadataReference(), typeLib_v1_Comp.ToMetadataReference()], + parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute()); + + comp1.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle); + comp1.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), + // (8,22): error CS0656: Missing compiler required member 'System.Reflection.MethodBase.GetMethodFromHandle' + // return () => C1.M1; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C1.M1").WithArguments("System.Reflection.MethodBase", "GetMethodFromHandle").WithLocation(8, 22) + ); + } + + [Fact] + public void GetMethodFromHandle_WellKnown_02() + { + var corLib_v1 = @" +namespace System +{ + public class Object + {} + + public class Void + {} + + public class ValueType + {} + + public struct RuntimeTypeHandle + {} + + public struct RuntimeMethodHandle + {} + + public struct Int32 + {} + + public abstract class Delegate + {} + + public abstract class MulticastDelegate : Delegate + {} + + public delegate void Action(); + + public delegate TResult Func(); + + public struct Nullable + {} +} + +namespace System.Collections.Generic +{ + public interface IEnumerable + {} +} +"; + var corLib_v1_Comp = CreateEmptyCompilation(corLib_v1, assemblyName: "corLib"); + + var typeLib_v1 = @" +namespace System +{ + public class Type + { + public static Type GetTypeFromHandle(RuntimeTypeHandle handle) => null; + } +} +namespace System.Reflection +{ + public abstract partial class MethodBase + { + public static MethodBase GetMethodFromHandle(RuntimeMethodHandle handle) => null; + public static MethodBase GetMethodFromHandle(RuntimeMethodHandle handle, RuntimeTypeHandle declaringType) => null; + } + + public abstract partial class MethodInfo : MethodBase + { + public virtual Delegate CreateDelegate(Type delegateType) => null; + public virtual Delegate CreateDelegate(Type delegateType, object target) => null; + } +} + +namespace System.Linq.Expressions +{ + public abstract class Expression + { + public static ConstantExpression Constant (object value) => null; + public static ConstantExpression Constant (object value, Type type) => null; + + public static MethodCallExpression Call (Expression instance, System.Reflection.MethodInfo method, Expression[] arguments) => null; + + public static UnaryExpression Convert (Expression expression, Type type) => null; + public static UnaryExpression Convert (Expression expression, Type type, System.Reflection.MethodInfo method) => null; + + public static Expression Lambda (Expression body, ParameterExpression[] parameters) => null; + } + + public abstract class LambdaExpression : Expression + {} + + public abstract class Expression : LambdaExpression + {} + + public class ConstantExpression : Expression + {} + + public class ParameterExpression : Expression + {} + + public class MethodCallExpression : Expression + {} + + public sealed class UnaryExpression : Expression + {} +} +"; + + var typeLib_v1_Comp = CreateEmptyCompilation(typeLib_v1, references: [corLib_v1_Comp.ToMetadataReference()], assemblyName: "typeLib"); + + typeLib_v1_Comp.VerifyDiagnostics(); + + var source1 = @" +#nullable disable + +public class Test +{ + public static System.Linq.Expressions.Expression> Expression() + { + return () => C1.M1; + } +} + +class C1 +{ + public static void M1() {} +} +"; + var comp1 = CreateEmptyCompilation( + source1, references: [corLib_v1_Comp.ToMetadataReference(), typeLib_v1_Comp.ToMetadataReference()], + parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute()); + + comp1.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle); + comp1.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle2); + + var comp1Ref = comp1.EmitToImageReference(); + + var corLib_v2 = @" +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Object))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(void))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ValueType))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeTypeHandle))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeMethodHandle))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Int32))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Action))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Func<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Nullable<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.IEnumerable<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Delegate))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.MulticastDelegate))] +"; + var corLib_v2_Comp = CreateCompilation(corLib_v2, assemblyName: "corLib"); + + var typeLib_v2 = @" +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Type))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.MethodBase))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.MethodInfo))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.Expression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.LambdaExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.Expression<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.ConstantExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.ParameterExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.MethodCallExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.UnaryExpression))] +"; + + var typeLib_v2_Comp = CreateCompilation(typeLib_v2, assemblyName: "typeLib"); + + var source2 = @" +class Program +{ + static void Main() + { + System.Console.WriteLine(Test.Expression()); + } +} +"; + + var comp = CreateCompilation(source2, references: [corLib_v2_Comp.ToMetadataReference(), typeLib_v2_Comp.ToMetadataReference(), comp1Ref], options: TestOptions.DebugExe); + + CompileAndVerify(comp, expectedOutput: "() => Convert(Void M1().CreateDelegate(System.Action, null)" + + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Action" : "") + + ")"); + + comp1 = CreateEmptyCompilation( + source1, references: [corLib_v1_Comp.ToMetadataReference(), typeLib_v1_Comp.ToMetadataReference()], + parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute()); + + comp1.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2); + comp1.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), + // (8,22): error CS0656: Missing compiler required member 'System.Reflection.MethodBase.GetMethodFromHandle' + // return () => C1.M1; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C1.M1").WithArguments("System.Reflection.MethodBase", "GetMethodFromHandle").WithLocation(8, 22) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs index bf23e1d535416..f40628bbb009d 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs @@ -49419,7 +49419,7 @@ static explicit operator byte(I1 x) public void RuntimeFeature_01() { var compilation1 = CreateCompilation("", options: TestOptions.DebugDll, - references: new[] { Net461.mscorlib }, + references: new[] { Net461.References.mscorlib }, targetFramework: TargetFramework.Empty); Assert.False(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); @@ -49430,7 +49430,7 @@ public void RuntimeFeature_01() public void RuntimeFeature_02() { var compilation1 = CreateCompilation("", options: TestOptions.DebugDll, - references: new[] { Net70.SystemRuntime }, + references: new[] { Net70.References.SystemRuntime }, targetFramework: TargetFramework.Empty); Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); @@ -49730,7 +49730,7 @@ public static class RuntimeFeature "; var compilation1 = CreateCompilation(source, options: TestOptions.DebugDll, - references: new[] { Net461.mscorlib }, + references: new[] { Net461.References.mscorlib }, targetFramework: TargetFramework.Empty); Assert.False(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs index d96d72871507c..d8031c75dcea4 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs @@ -4143,10 +4143,10 @@ class C var comp = CreateCompilationWithRequiredMembers(code); comp.VerifyDiagnostics( - // (8,19): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (8,19): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public string P2 { get; set; } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P2").WithArguments("property", "P2").WithLocation(8, 19), - // (9,19): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (9,19): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public string F2; Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "F2").WithArguments("field", "F2").WithLocation(9, 19) ); @@ -4175,16 +4175,16 @@ public C(int _) {} var comp = CreateCompilationWithRequiredMembers(code); comp.VerifyDiagnostics( - // (11,12): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (11,12): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "P2").WithLocation(11, 12), - // (11,12): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (11,12): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("field", "F2").WithLocation(11, 12), - // (15,12): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (15,12): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C(int _) {} Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "P2").WithLocation(15, 12), - // (15,12): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (15,12): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C(int _) {} Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("field", "F2").WithLocation(15, 12) ); @@ -4229,10 +4229,10 @@ public S() {} var comp = CreateCompilationWithRequiredMembers(code); comp.VerifyDiagnostics( - // (11,12): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (11,12): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public S() {} Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("property", "P2").WithLocation(11, 12), - // (11,12): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (11,12): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public S() {} Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "F2").WithLocation(11, 12) ); @@ -4368,10 +4368,10 @@ public C() { } var comp = CreateCompilationWithRequiredMembers(new[] { code, MemberNotNullAttributeDefinition }); comp.VerifyDiagnostics( - // (9,12): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (9,12): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "Property").WithLocation(9, 12), - // (9,12): warning CS8618: Non-nullable field '_field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (9,12): warning CS8618: Non-nullable field '_field' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("field", "_field").WithLocation(9, 12) ); @@ -4396,10 +4396,10 @@ public C() { } var comp = CreateCompilationWithRequiredMembers(new[] { code, MemberNotNullAttributeDefinition }); comp.VerifyDiagnostics( - // (9,12): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (9,12): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "Property").WithLocation(9, 12), - // (9,12): warning CS8618: Non-nullable field '_field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (9,12): warning CS8618: Non-nullable field '_field' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public C() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("field", "_field").WithLocation(9, 12) ); @@ -4447,7 +4447,7 @@ public C() { } var comp = CreateCompilationWithRequiredMembers(new[] { code, MemberNotNullAttributeDefinition }); comp.VerifyDiagnostics( - // (8,12): warning CS8618: Non-nullable property 'Prop3' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (8,12): warning CS8618: Non-nullable property 'Prop3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "Prop3").WithLocation(8, 12) ); @@ -4607,10 +4607,10 @@ public C(bool unused) : this() var comp = CreateCompilationWithRequiredMembers(new[] { code, MemberNotNullAttributeDefinition }); comp.VerifyDiagnostics( - // (9,12): warning CS8618: Non-nullable property 'Property2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (9,12): warning CS8618: Non-nullable property 'Property2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "Property2").WithLocation(9, 12), - // (9,12): warning CS8618: Non-nullable property 'Property1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (9,12): warning CS8618: Non-nullable property 'Property1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "Property1").WithLocation(9, 12) ); @@ -4782,7 +4782,7 @@ private Derived() // (4,20): warning CS0169: The field 'Derived._field' is never used // private string _field; Diagnostic(ErrorCode.WRN_UnreferencedField, "_field").WithArguments("Derived._field").WithLocation(4, 20), - // (5,13): warning CS8618: Non-nullable field '_field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (5,13): warning CS8618: Non-nullable field '_field' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // private Derived() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("field", "_field").WithLocation(5, 13) }; @@ -4825,7 +4825,7 @@ public Derived() """; var expectedDiagnostics = new[] { - // (6,12): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (6,12): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived() { } Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Property").WithLocation(6, 12), // (8,9): warning CS8602: Dereference of a possibly null reference. @@ -5131,7 +5131,7 @@ public Derived() : base() // 2 var comp = CreateCompilationWithRequiredMembers(code); comp.VerifyDiagnostics( - // (9,15): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (9,15): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // protected Base() {} // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Base").WithArguments("property", "Prop1").WithLocation(9, 15), // (17,24): error CS9039: This constructor must add 'SetsRequiredMembers' because it chains to a constructor that has that attribute. @@ -5195,16 +5195,16 @@ public Derived() : this(0) var comp = CreateCompilationWithRequiredMembers(new[] { derived, @base }); comp.VerifyDiagnostics( - // (10,12): warning CS8618: Non-nullable property 'Prop3' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'Prop3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(int unused) : base() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop3").WithLocation(10, 12), - // (10,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(int unused) : base() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop1").WithLocation(10, 12), - // (16,12): warning CS8618: Non-nullable property 'Prop3' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (16,12): warning CS8618: Non-nullable property 'Prop3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop3").WithLocation(16, 12), - // (16,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (16,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop1").WithLocation(16, 12), // (21,24): error CS9039: This constructor must add 'SetsRequiredMembers' because it chains to a constructor that has that attribute. @@ -5215,16 +5215,16 @@ public Derived() : this(0) var baseComp = CreateCompilationWithRequiredMembers(@base); comp = CreateCompilation(derived, new[] { baseComp.EmitToImageReference() }); comp.VerifyDiagnostics( - // (10,12): warning CS8618: Non-nullable property 'Prop3' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'Prop3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(int unused) : base() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop3").WithLocation(10, 12), - // (10,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(int unused) : base() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop1").WithLocation(10, 12), - // (16,12): warning CS8618: Non-nullable property 'Prop3' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (16,12): warning CS8618: Non-nullable property 'Prop3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop3").WithLocation(16, 12), - // (16,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (16,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop1").WithLocation(16, 12), // (21,24): error CS9039: This constructor must add 'SetsRequiredMembers' because it chains to a constructor that has that attribute. @@ -5277,16 +5277,16 @@ public Derived() : this(0) var comp = CreateCompilationWithRequiredMembers(code); comp.VerifyDiagnostics( - // (17,12): warning CS8618: Non-nullable field 'Field3' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (17,12): warning CS8618: Non-nullable field 'Field3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public Derived(int unused) : base() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("field", "Field3").WithLocation(17, 12), - // (17,12): warning CS8618: Non-nullable field 'Field1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (17,12): warning CS8618: Non-nullable field 'Field1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public Derived(int unused) : base() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("field", "Field1").WithLocation(17, 12), - // (23,12): warning CS8618: Non-nullable field 'Field3' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (23,12): warning CS8618: Non-nullable field 'Field3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public Derived(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("field", "Field3").WithLocation(23, 12), - // (23,12): warning CS8618: Non-nullable field 'Field1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (23,12): warning CS8618: Non-nullable field 'Field1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // public Derived(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("field", "Field1").WithLocation(23, 12), // (28,24): error CS9039: This constructor must add 'SetsRequiredMembers' because it chains to a constructor that has that attribute. @@ -5341,16 +5341,16 @@ public Derived() : this(0) var comp = CreateCompilationWithRequiredMembers(new[] { derived, @base }); comp.VerifyDiagnostics( - // (10,12): warning CS8618: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(int unused) : base() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop2").WithLocation(10, 12), - // (10,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(int unused) : base() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop1").WithLocation(10, 12), - // (15,12): warning CS8618: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (15,12): warning CS8618: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop2").WithLocation(15, 12), - // (15,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (15,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop1").WithLocation(15, 12), // (19,24): error CS9039: This constructor must add 'SetsRequiredMembers' because it chains to a constructor that has that attribute. @@ -5361,16 +5361,16 @@ public Derived() : this(0) var baseComp = CreateCompilationWithRequiredMembers(@base); comp = CreateCompilation(derived, new[] { baseComp.EmitToImageReference() }); comp.VerifyDiagnostics( - // (10,12): warning CS8618: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(int unused) : base() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop2").WithLocation(10, 12), - // (10,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (10,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(int unused) : base() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop1").WithLocation(10, 12), - // (15,12): warning CS8618: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (15,12): warning CS8618: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop2").WithLocation(15, 12), - // (15,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (15,12): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public Derived(bool unused) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Prop1").WithLocation(15, 12), // (19,24): error CS9039: This constructor must add 'SetsRequiredMembers' because it chains to a constructor that has that attribute. @@ -5732,7 +5732,7 @@ public record Derived() : Base; var comp = CreateCompilationWithRequiredMembers(code); comp.VerifyDiagnostics( - // (9,15): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (9,15): warning CS8618: Non-nullable property 'Prop1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // protected Base() {} // 1 Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Base").WithArguments("property", "Prop1").WithLocation(9, 15), // (12,15): error CS9039: This constructor must add 'SetsRequiredMembers' because it chains to a constructor that has that attribute. diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/NullablePublicAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/NullablePublicAPITests.cs index 75ebd9324b0cc..afdf7d50836b5 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/NullablePublicAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/NullablePublicAPITests.cs @@ -1557,7 +1557,7 @@ public C() var comp = CreateCompilation(source, options: WithNullableEnable()); comp.VerifyDiagnostics( - // (5,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // (5,12): warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public C() Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "Prop").WithLocation(5, 12)); @@ -1651,7 +1651,7 @@ static void Main() var comp = CreateCompilation(source, options: WithNullableEnable()); comp.VerifyDiagnostics( - // (6,27): warning CS8618: Non-nullable field 's_data' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // (6,27): warning CS8618: Non-nullable field 's_data' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. // private static string s_data; Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "s_data").WithArguments("field", "s_data").WithLocation(6, 27), // (6,27): warning CS0649: Field 'C.s_data' is never assigned to, and will always have its default value null diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs index a7b6120dc07cc..9f5490d05adac 100644 --- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs +++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs @@ -433,6 +433,7 @@ public void WarningLevel_2() case ErrorCode.WRN_DynamicDispatchToParamsCollectionMethod: case ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer: case ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor: + case ErrorCode.INF_TooManyBoundLambdas: Assert.Equal(1, ErrorFacts.GetWarningLevel(errorCode)); break; case ErrorCode.WRN_InvalidVersionFormat: diff --git a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs index 337c50884f755..64fc656f659fb 100644 --- a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs @@ -572,6 +572,51 @@ public static int Main () Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(5, 25)); } + [Fact] + public void CS1035AtColonParsedAsComment_01() + { + var test = """ + var x = @:; + """; + + ParsingTests.ParseAndValidate(test, + // (1,9): error CS1056: Unexpected character '@' + // var x = @:; + Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "@").WithArguments("@").WithLocation(1, 9), + // (1,12): error CS1733: Expected expression + // var x = @:; + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 12), + // (1,12): error CS1002: ; expected + // var x = @:; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 12)); + } + + [Fact] + public void CS1035AtColonParsedAsComment_02() + { + var test = """ + @:
test
+ """; + + ParsingTests.ParseAndValidate(test, + // (1,1): error CS1056: Unexpected character '@' + // @:
test
+ Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "@").WithArguments("@").WithLocation(1, 1)); + } + + [Fact] + public void CS1035AtColonParsedAsComment_03() + { + var test = """ + @: M() {} + """; + + ParsingTests.ParseAndValidate(test, + // (1,1): error CS1056: Unexpected character '@' + // @: M() {} + Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "@").WithArguments("@").WithLocation(1, 1)); + } + [Fact, WorkItem(526993, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/526993")] public void CS1039ERR_UnterminatedStringLit() { diff --git a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs index 85afeca5a8582..462c92901099f 100644 --- a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs @@ -440,6 +440,143 @@ public void TestMixedMultiLineCommentTerminators_01(char outsideDelimiter, char Assert.Equal(SyntaxKind.MultiLineCommentTrivia, trivia[0].Kind()); } + [Fact] + [Trait("Feature", "Comments")] + public void TestAtColonTreatedAsComment_RazorRecovery() + { + var text = "@: More text"; + var token = LexToken(text); + + Assert.NotEqual(default, token); + Assert.Equal(SyntaxKind.EndOfFileToken, token.Kind()); + Assert.Equal(text, token.ToFullString()); + var errors = token.Errors(); + errors.Verify( + // error CS1056: Unexpected character '@' + TestBase.Diagnostic(ErrorCode.ERR_UnexpectedCharacter).WithArguments("@").WithLocation(1, 1)); + var trivia = token.GetLeadingTrivia().ToArray(); + Assert.Equal(1, trivia.Length); + Assert.NotEqual(default, trivia[0]); + Assert.Equal(SyntaxKind.SingleLineCommentTrivia, trivia[0].Kind()); + } + + [Fact] + [Trait("Feature", "Comments")] + public void TestAtColonTreatedAsCommentAsTrailingTrivia_RazorRecovery() + { + var text = """ + Identifier @: More text + // Regular comment + SecondIdentifier + """; + var tokens = Lex(text).ToList(); + var token = tokens[0]; + + Assert.NotEqual(default, token); + Assert.Equal(SyntaxKind.IdentifierToken, token.Kind()); + var errors = token.Errors(); + errors.Verify( + // error CS1056: Unexpected character '@' + TestBase.Diagnostic(ErrorCode.ERR_UnexpectedCharacter).WithArguments("@").WithLocation(1, 1)); + var trivia = token.GetLeadingTrivia().ToArray(); + Assert.Equal(0, trivia.Length); + trivia = token.GetTrailingTrivia().ToArray(); + Assert.Equal(3, trivia.Length); + Assert.NotEqual(default, trivia[1]); + Assert.Equal(SyntaxKind.SingleLineCommentTrivia, trivia[1].Kind()); + Assert.Equal("@: More text", trivia[1].ToFullString()); + + token = tokens[1]; + Assert.NotEqual(default, token); + Assert.Equal(SyntaxKind.IdentifierToken, token.Kind()); + Assert.Equal(""" + // Regular comment + SecondIdentifier + """, token.ToFullString()); + } + + [Fact] + [Trait("Feature", "Comments")] + public void TestAtColonTreatedAsComment_TrailingMultiLine_RazorRecovery() + { + var text = """ + @: /* + Identifier + */ + """; + + var tokens = Lex(text).ToList(); + var token = tokens[0]; + + Assert.NotEqual(default, token); + Assert.Equal(SyntaxKind.IdentifierToken, token.Kind()); + Assert.Equal(""" + @: /* + Identifier + + """, token.ToFullString()); + var errors = token.Errors(); + errors.Verify( + // error CS1056: Unexpected character '@' + TestBase.Diagnostic(ErrorCode.ERR_UnexpectedCharacter).WithArguments("@").WithLocation(1, 1)); + var trivia = token.GetLeadingTrivia().ToArray(); + Assert.Equal(2, trivia.Length); + Assert.NotEqual(default, trivia[0]); + Assert.Equal(SyntaxKind.SingleLineCommentTrivia, trivia[0].Kind()); + Assert.Equal("@: /*", trivia[0].ToFullString()); + } + + [Fact] + [Trait("Feature", "Comments")] + public void TestAtColonTreatedAsComment_PreprocessorDisabled_RazorRecovery() + { + var text = """ + #if false + @: + #endif + """; + + var token = LexToken(text); + + Assert.NotEqual(default, token); + Assert.Equal(SyntaxKind.EndOfFileToken, token.Kind()); + Assert.Equal(text, token.ToFullString()); + var errors = token.Errors(); + errors.Verify(); + var trivia = token.GetLeadingTrivia().ToArray(); + Assert.Equal(3, trivia.Length); + Assert.Equal(SyntaxKind.IfDirectiveTrivia, trivia[0].Kind()); + Assert.Equal(SyntaxKind.DisabledTextTrivia, trivia[1].Kind()); + Assert.Equal(SyntaxKind.EndIfDirectiveTrivia, trivia[2].Kind()); + } + + [Fact] + [Trait("Feature", "Comments")] + public void TestAtColonTreatedAsComment_PreprocessorEnabled_RazorRecovery() + { + var text = """ + #if true + @: + #endif + """; + + var token = LexToken(text); + + Assert.NotEqual(default, token); + Assert.Equal(SyntaxKind.EndOfFileToken, token.Kind()); + Assert.Equal(text, token.ToFullString()); + var errors = token.Errors(); + errors.Verify( + // error CS1056: Unexpected character '@' + TestBase.Diagnostic(ErrorCode.ERR_UnexpectedCharacter).WithArguments("@").WithLocation(1, 1)); + var trivia = token.GetLeadingTrivia().ToArray(); + Assert.Equal(4, trivia.Length); + Assert.Equal(SyntaxKind.IfDirectiveTrivia, trivia[0].Kind()); + Assert.Equal(SyntaxKind.SingleLineCommentTrivia, trivia[1].Kind()); + Assert.Equal(SyntaxKind.EndOfLineTrivia, trivia[2].Kind()); + Assert.Equal(SyntaxKind.EndIfDirectiveTrivia, trivia[3].Kind()); + } + [Fact] [Trait("Feature", "Comments")] public void TestCommentWithTextWindowSentinel() diff --git a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/SyntaxTokenParserTests.cs b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/SyntaxTokenParserTests.cs new file mode 100644 index 0000000000000..a943d55c7d1d5 --- /dev/null +++ b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/SyntaxTokenParserTests.cs @@ -0,0 +1,276 @@ +// Licensed to the .NET Foundation under one or more 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.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.LexicalAndXml; + +public class SyntaxTokenParserTests +{ + [Fact] + public void TestDispose() + { + var sourceText = SourceText.From("class C { }"); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + parser.Dispose(); + Assert.Throws(() => parser.ParseNextToken()); + parser.Dispose(); + } + + [Fact] + public void TestParseNext() + { + var sourceText = SourceText.From(""" + // Hello world + class C + { + + } + """.NormalizeLineEndings()); + + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 22), """ + // Hello world + class + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.IdentifierToken, expectedContextualKind: SyntaxKind.None, new TextSpan(22, 3), """ + C + + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.OpenBraceToken, expectedContextualKind: SyntaxKind.None, new TextSpan(25, 3), """ + { + + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.CloseBraceToken, expectedContextualKind: SyntaxKind.None, new TextSpan(28, 3), """ + + } + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.EndOfFileToken, expectedContextualKind: SyntaxKind.None, new TextSpan(31, 0), "", parser.ParseNextToken()); + AssertToken(expectedKind: SyntaxKind.EndOfFileToken, expectedContextualKind: SyntaxKind.None, new TextSpan(31, 0), "", parser.ParseNextToken()); + } + + [Fact] + public void DocumentCommentTriviaIsHandled() + { + var sourceText = SourceText.From(""" + /// + /// Hello world + /// + class C + { + + } + """.NormalizeLineEndings()); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + var result = parser.ParseNextToken(); + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 54), """ + /// + /// Hello world + /// + class + """, result); + + var docCommentTrivia = result.Token.GetLeadingTrivia()[0]; + Assert.Equal(SyntaxKind.SingleLineDocumentationCommentTrivia, docCommentTrivia.Kind()); + Assert.NotNull(docCommentTrivia.GetStructure()); + + AssertToken(expectedKind: SyntaxKind.IdentifierToken, expectedContextualKind: SyntaxKind.None, new TextSpan(54, 3), """ + C + + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.OpenBraceToken, expectedContextualKind: SyntaxKind.None, new TextSpan(57, 3), """ + { + + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.CloseBraceToken, expectedContextualKind: SyntaxKind.None, new TextSpan(60, 3), """ + + } + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.EndOfFileToken, expectedContextualKind: SyntaxKind.None, new TextSpan(63, 0), "", parser.ParseNextToken()); + } + + [Fact] + public void DirectiveContextIsPreservedAcrossParseNextToken() + { + var sourceText = SourceText.From(""" + #if true + class C + { + + #else + } + #endif + """.NormalizeLineEndings()); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 16), """ + #if true + class + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.IdentifierToken, expectedContextualKind: SyntaxKind.None, new TextSpan(16, 3), """ + C + + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.OpenBraceToken, expectedContextualKind: SyntaxKind.None, new TextSpan(19, 3), """ + { + + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.EndOfFileToken, expectedContextualKind: SyntaxKind.None, new TextSpan(22, 18), """ + + #else + } + #endif + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.EndOfFileToken, expectedContextualKind: SyntaxKind.None, new TextSpan(40, 0), "", parser.ParseNextToken()); + } + + [Fact] + public void SkipForwardTo() + { + var sourceText = SourceText.From(""" + This is not C# + + // Hello world + class C + { + + } + """.NormalizeLineEndings()); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + parser.SkipForwardTo(18); + + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(18, 22), """ + // Hello world + class + """, parser.ParseNextToken()); + + parser.SkipForwardTo(43); + + AssertToken(expectedKind: SyntaxKind.OpenBraceToken, expectedContextualKind: SyntaxKind.None, new TextSpan(43, 3), """ + { + + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.CloseBraceToken, expectedContextualKind: SyntaxKind.None, new TextSpan(46, 3), """ + + } + """, parser.ParseNextToken()); + } + + [Fact] + public void SkipForwardTo2() + { + var sourceText = SourceText.From("""class"""); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + parser.SkipForwardTo(1); + + AssertToken(expectedKind: SyntaxKind.IdentifierToken, expectedContextualKind: SyntaxKind.None, new TextSpan(1, 4), """lass""", parser.ParseNextToken()); + } + + [Fact] + public void SkipForwardTo_PastDocumentEnd() + { + var sourceText = SourceText.From("class C { }"); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + parser.SkipForwardTo(100); + AssertToken(expectedKind: SyntaxKind.EndOfFileToken, expectedContextualKind: SyntaxKind.None, new TextSpan(100, 0), "", parser.ParseNextToken()); + } + + [Fact] + public void SkipForwardTo_CannotSkipBack() + { + var sourceText = SourceText.From("class C { }"); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + parser.SkipForwardTo(0); + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 6), "class ", parser.ParseNextToken()); + Assert.Throws(() => parser.SkipForwardTo(0)); + } + + [Fact] + public void ResetToPreservedDirectiveContext() + { + var sourceText = SourceText.From(""" + #if true + class C + { + + #else + } + #endif + """.NormalizeLineEndings()); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 16), """ + #if true + class + """, parser.ParseNextToken()); + + SyntaxTokenParser.Result cTokenResult = parser.ParseNextToken(); + AssertToken(expectedKind: SyntaxKind.IdentifierToken, expectedContextualKind: SyntaxKind.None, new TextSpan(16, 3), """ + C + + """, cTokenResult); + + verifyAfterC(parser); + + parser.ResetTo(cTokenResult); + + Assert.Equal(cTokenResult, parser.ParseNextToken()); + + verifyAfterC(parser); + + static void verifyAfterC(SyntaxTokenParser parser) + { + AssertToken(expectedKind: SyntaxKind.OpenBraceToken, expectedContextualKind: SyntaxKind.None, new TextSpan(19, 3), """ + { + + """, parser.ParseNextToken()); + + AssertToken(expectedKind: SyntaxKind.EndOfFileToken, expectedContextualKind: SyntaxKind.None, new TextSpan(22, 18), """ + + #else + } + #endif + """, parser.ParseNextToken()); + } + } + + [Fact] + public void ResultContextualKind() + { + var sourceText = SourceText.From("when identifier class"); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + AssertToken(expectedKind: SyntaxKind.IdentifierToken, expectedContextualKind: SyntaxKind.WhenKeyword, new TextSpan(0, 5), "when ", parser.ParseNextToken()); + AssertToken(expectedKind: SyntaxKind.IdentifierToken, expectedContextualKind: SyntaxKind.None, new TextSpan(5, 11), "identifier ", parser.ParseNextToken()); + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(16, 5), "class", parser.ParseNextToken()); + } + + private static void AssertToken(SyntaxKind expectedKind, SyntaxKind expectedContextualKind, TextSpan expectedFullSpan, string expectedText, SyntaxTokenParser.Result result) + { + Assert.Equal(expectedKind, result.Token.Kind()); + Assert.Equal(expectedContextualKind, result.ContextualKind); + AssertEx.Equal(expectedText.NormalizeLineEndings(), result.Token.ToFullString()); + Assert.Null(result.Token.Parent); + Assert.Equal(expectedFullSpan, result.Token.FullSpan); + } +} diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs index 971eab148dc8e..6a8d436530645 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs @@ -776,17 +776,12 @@ public void GenericAsyncTask_01() foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) { UsingDeclaration("async Task' expected // async Task' expected - // async Task").WithLocation(1, 41) + Diagnostic(ErrorCode.ERR_SyntaxError, "Method").WithArguments(">").WithLocation(1, 35) ); - N(SyntaxKind.IncompleteMember); + + N(SyntaxKind.MethodDeclaration); { N(SyntaxKind.AsyncKeyword); N(SyntaxKind.GenericName); @@ -807,14 +802,16 @@ public void GenericAsyncTask_01() N(SyntaxKind.IdentifierToken, "SomeType"); } } - M(SyntaxKind.CommaToken); - N(SyntaxKind.IdentifierName); - { - N(SyntaxKind.IdentifierToken, "Method"); - } M(SyntaxKind.GreaterThanToken); } } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); } EOF(); } @@ -827,17 +824,12 @@ public void GenericPublicTask_01() foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) { UsingDeclaration("public Task' expected + // (1,36): error CS1003: Syntax error, '>' expected // public Task").WithLocation(1, 42) + Diagnostic(ErrorCode.ERR_SyntaxError, "Method").WithArguments(">").WithLocation(1, 36) ); - N(SyntaxKind.IncompleteMember); + + N(SyntaxKind.MethodDeclaration); { N(SyntaxKind.PublicKeyword); N(SyntaxKind.GenericName); @@ -858,14 +850,16 @@ public void GenericPublicTask_01() N(SyntaxKind.IdentifierToken, "SomeType"); } } - M(SyntaxKind.CommaToken); - N(SyntaxKind.IdentifierName); - { - N(SyntaxKind.IdentifierToken, "Method"); - } M(SyntaxKind.GreaterThanToken); } } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); } EOF(); } @@ -10477,5 +10471,6862 @@ public class Class } EOF(); } + + #region Missing > after generic + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) GetAllValues(Type t) + { + if (!t.IsEnum) + throw new ArgumentException("no good"); + + return Enum.GetValues(t).Cast().Select(e => (e.ToString(), e.ToString())); + } + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) GetAllValues(Type t) + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.IfStatement); + { + N(SyntaxKind.IfKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.LogicalNotExpression); + { + N(SyntaxKind.ExclamationToken); + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "IsEnum"); + } + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.ThrowStatement); + { + N(SyntaxKind.ThrowKeyword); + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ArgumentException"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.StringLiteralExpression); + { + N(SyntaxKind.StringLiteralToken, "\"no good\""); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.ReturnStatement); + { + N(SyntaxKind.ReturnKeyword); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Enum"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "GetValues"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "t"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.DotToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Cast"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Enum"); + } + N(SyntaxKind.GreaterThanToken); + } + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Select"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ToString"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ToString"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + // Parser expects: + // static IEnumerable<(string Value, string Description)> A(Type t); + const string source = + """ + static IEnumerable<(string Value, string Description) A' expected + // static IEnumerable<(string Value, string Description) A").WithLocation(1, 55), + // (1,59): error CS1003: Syntax error, ',' expected + // static IEnumerable<(string Value, string Description) A' expected + // static IEnumerable<(string Value, string Description) A").WithLocation(1, 71)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "A"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), int GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,60): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), int GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 60)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method04() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), A::X GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,61): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), A::X GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 61)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.AliasQualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ColonColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method05() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), X.Y GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,60): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), X.Y GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 60)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Y"); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method06() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), A' expected + // static IEnumerable<(string Value, string Description), A").WithLocation(1, 60), + // (1,60): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), A").WithLocation(1, 60)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "A"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + M(SyntaxKind.GreaterThanToken); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method07() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), A GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,61): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), A GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 61)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "A"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.GreaterThanToken); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method08() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), ref int GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,64): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), ref int GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 64)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.RefType); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method09() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), int* GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,61): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), int* GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 61)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PointerType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.AsteriskToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method10() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), int[] GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,62): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), int[] GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 62)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.ArrayType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.ArrayRankSpecifier); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.OmittedArraySizeExpression); + { + N(SyntaxKind.OmittedArraySizeExpressionToken); + } + N(SyntaxKind.CloseBracketToken); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method11() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), string[] GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,65): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), string[] GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 65)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.ArrayType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.ArrayRankSpecifier); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.OmittedArraySizeExpression); + { + N(SyntaxKind.OmittedArraySizeExpressionToken); + } + N(SyntaxKind.CloseBracketToken); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method12() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), int*[] GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,63): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), int*[] GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 63)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.ArrayType); + { + N(SyntaxKind.PointerType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.AsteriskToken); + } + N(SyntaxKind.ArrayRankSpecifier); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.OmittedArraySizeExpression); + { + N(SyntaxKind.OmittedArraySizeExpressionToken); + } + N(SyntaxKind.CloseBracketToken); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method13() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description), (X[], Y.Z) GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,67): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description), (X[], Y.Z) GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 67)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.ArrayType); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ArrayRankSpecifier); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.OmittedArraySizeExpression); + { + N(SyntaxKind.OmittedArraySizeExpressionToken); + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Y"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Z"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method14() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) A.GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,1): error CS1073: Unexpected token '(' + // static IEnumerable<(string Value, string Description) A.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_UnexpectedToken, "static IEnumerable<(string Value, string Description) A.GetAllValues").WithArguments("(").WithLocation(1, 1), + // (1,55): error CS1003: Syntax error, ',' expected + // static IEnumerable<(string Value, string Description) A.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "A").WithArguments(",").WithLocation(1, 55), + // (1,69): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) A.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(">").WithLocation(1, 69)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method15() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) A::GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,1): error CS1073: Unexpected token '(' + // static IEnumerable<(string Value, string Description) A::GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_UnexpectedToken, "static IEnumerable<(string Value, string Description) A::GetAllValues").WithArguments("(").WithLocation(1, 1), + // (1,55): error CS1003: Syntax error, ',' expected + // static IEnumerable<(string Value, string Description) A::GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "A").WithArguments(",").WithLocation(1, 55), + // (1,70): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) A::GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(">").WithLocation(1, 70)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.AliasQualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ColonColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method16() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) A::B.GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,1): error CS1073: Unexpected token '(' + // static IEnumerable<(string Value, string Description) A::B.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_UnexpectedToken, "static IEnumerable<(string Value, string Description) A::B.GetAllValues").WithArguments("(").WithLocation(1, 1), + // (1,55): error CS1003: Syntax error, ',' expected + // static IEnumerable<(string Value, string Description) A::B.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "A").WithArguments(",").WithLocation(1, 55), + // (1,72): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) A::B.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(">").WithLocation(1, 72)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.AliasQualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ColonColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method17() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) GetAllValues' expected + // static IEnumerable<(string Value, string Description) GetAllValues").WithLocation(1, 55), + // (1,69): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) GetAllValues").WithLocation(1, 69)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method18() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments(">").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method19() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) B.GetAllValues' expected + // static IEnumerable<(string Value, string Description) B.GetAllValues").WithLocation(1, 71), + // (1,71): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) B.GetAllValues").WithLocation(1, 71)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method20() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) B.GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,1): error CS1073: Unexpected token '(' + // static IEnumerable<(string Value, string Description) B.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_UnexpectedToken, "static IEnumerable<(string Value, string Description) B.GetAllValues").WithArguments("(").WithLocation(1, 1), + // (1,55): error CS1003: Syntax error, ',' expected + // static IEnumerable<(string Value, string Description) B.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "B").WithArguments(",").WithLocation(1, 55), + // (1,72): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) B.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(">").WithLocation(1, 72)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method21() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) A::B.GetAllValues' expected + // static IEnumerable<(string Value, string Description) A::B.GetAllValues").WithLocation(1, 74), + // (1,74): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) A::B.GetAllValues").WithLocation(1, 74)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.AliasQualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ColonColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + } + N(SyntaxKind.DotToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method22() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) A::B.GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,1): error CS1073: Unexpected token '(' + // static IEnumerable<(string Value, string Description) A::B.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_UnexpectedToken, "static IEnumerable<(string Value, string Description) A::B.GetAllValues").WithArguments("(").WithLocation(1, 1), + // (1,55): error CS1003: Syntax error, ',' expected + // static IEnumerable<(string Value, string Description) A::B.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "A").WithArguments(",").WithLocation(1, 55), + // (1,75): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) A::B.GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(">").WithLocation(1, 75)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.AliasQualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ColonColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + } + N(SyntaxKind.DotToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method23() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) A::GetAllValues' expected + // static IEnumerable<(string Value, string Description) A::GetAllValues").WithLocation(1, 72), + // (1,72): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) A::GetAllValues").WithLocation(1, 72)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.AliasQualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ColonColonToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Method24() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IEnumerable<(string Value, string Description) A::GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,1): error CS1073: Unexpected token '(' + // static IEnumerable<(string Value, string Description) A::GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_UnexpectedToken, "static IEnumerable<(string Value, string Description) A::GetAllValues").WithArguments("(").WithLocation(1, 1), + // (1,55): error CS1003: Syntax error, ',' expected + // static IEnumerable<(string Value, string Description) A::GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "A").WithArguments(",").WithLocation(1, 55), + // (1,73): error CS1003: Syntax error, '>' expected + // static IEnumerable<(string Value, string Description) A::GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(">").WithLocation(1, 73)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.AliasQualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ColonColonToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) Type> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) Type> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "Type").WithArguments(",").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) int> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) int> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "int").WithArguments(",").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) Alias::X> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) Alias::X> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "Alias").WithArguments(",").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.AliasQualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Alias"); + } + N(SyntaxKind.ColonColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method04() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) Outer.Inner> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) Outer.Inner> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "Outer").WithArguments(",").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Outer"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Inner"); + } + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method05() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + // Unfortunately in this case we expect: + // static IDictionary<(string Value, string Description)> IEnumerable<@T>(GetAllValues @p1, (Type t) @p2); + const string source = + """ + static IDictionary<(string Value, string Description) IEnumerable GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, '>' expected + // static IDictionary<(string Value, string Description) IEnumerable GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "IEnumerable").WithArguments(">").WithLocation(1, 55), + // (1,67): error CS1001: Identifier expected + // static IDictionary<(string Value, string Description) IEnumerable GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "string").WithLocation(1, 67), + // (1,67): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) IEnumerable GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "string").WithArguments(",").WithLocation(1, 67), + // (1,75): error CS1003: Syntax error, '(' expected + // static IDictionary<(string Value, string Description) IEnumerable GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "GetAllValues").WithArguments("(").WithLocation(1, 75), + // (1,87): error CS1001: Identifier expected + // static IDictionary<(string Value, string Description) IEnumerable GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "(").WithLocation(1, 87), + // (1,87): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) IEnumerable GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(",").WithLocation(1, 87), + // (1,94): error CS8124: Tuple must contain at least two elements. + // static IDictionary<(string Value, string Description) IEnumerable GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_TupleTooFewElements, ")").WithLocation(1, 94), + // (1,95): error CS1001: Identifier expected + // static IDictionary<(string Value, string Description) IEnumerable GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ";").WithLocation(1, 95), + // (1,95): error CS1026: ) expected + // static IDictionary<(string Value, string Description) IEnumerable GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_CloseParenExpected, ";").WithLocation(1, 95)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + M(SyntaxKind.TypeParameter); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + M(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + } + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + M(SyntaxKind.CommaToken); + M(SyntaxKind.TupleElement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method06() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + // Unfortunately in this case we expect: + // static IDictionary<(string Value, string Description)> IEnumerable<@T>(GetAllValues @p1, (Type t) @p2); + const string source = + """ + static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, '>' expected + // static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "IEnumerable").WithArguments(">").WithLocation(1, 55), + // (1,67): error CS1001: Identifier expected + // static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "string").WithLocation(1, 67), + // (1,67): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "string").WithArguments(",").WithLocation(1, 67), + // (1,74): error CS1003: Syntax error, '(' expected + // static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, ">").WithArguments("(").WithLocation(1, 74), + // (1,74): error CS1001: Identifier expected + // static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ">").WithLocation(1, 74), + // (1,88): error CS1001: Identifier expected + // static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "(").WithLocation(1, 88), + // (1,88): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(",").WithLocation(1, 88), + // (1,95): error CS8124: Tuple must contain at least two elements. + // static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_TupleTooFewElements, ")").WithLocation(1, 95), + // (1,96): error CS1001: Identifier expected + // static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ";").WithLocation(1, 96), + // (1,96): error CS1026: ) expected + // static IDictionary<(string Value, string Description) IEnumerable> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_CloseParenExpected, ";").WithLocation(1, 96)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + M(SyntaxKind.TypeParameter); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + M(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "GetAllValues"); + } + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + M(SyntaxKind.CommaToken); + M(SyntaxKind.TupleElement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method07() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) (string, int)> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,1): error CS1073: Unexpected token '(' + // static IDictionary<(string Value, string Description) (string, int)> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_UnexpectedToken, "static IDictionary<(string Value, string Description) ").WithArguments("(").WithLocation(1, 1), + // (1,55): error CS1003: Syntax error, '>' expected + // static IDictionary<(string Value, string Description) (string, int)> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(">").WithLocation(1, 55)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method08() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) (X, X.X, X.X.X)> GetAllValues GetAllValues' expected + // static IDictionary<(string Value, string Description) (X, X.X, X.X.X)> GetAllValues").WithLocation(1, 55)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method09() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) (A GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,1): error CS1073: Unexpected token '(' + // static IDictionary<(string Value, string Description) (A GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_UnexpectedToken, "static IDictionary<(string Value, string Description) ").WithArguments("(").WithLocation(1, 1), + // (1,55): error CS1003: Syntax error, '>' expected + // static IDictionary<(string Value, string Description) (A GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(">").WithLocation(1, 55)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method10() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) (A, C.D)> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,1): error CS1073: Unexpected token '(' + // static IDictionary<(string Value, string Description) (A, C.D)> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_UnexpectedToken, "static IDictionary<(string Value, string Description) ").WithArguments("(").WithLocation(1, 1), + // (1,55): error CS1003: Syntax error, '>' expected + // static IDictionary<(string Value, string Description) (A, C.D)> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(">").WithLocation(1, 55)); + + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method11() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) int*> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) int*> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "int").WithArguments(",").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.PointerType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.AsteriskToken); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method12() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) void*> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) void*> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "void").WithArguments(",").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.PointerType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.AsteriskToken); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method13() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) String**> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) String**> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "String").WithArguments(",").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.PointerType); + { + N(SyntaxKind.PointerType); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "String"); + } + N(SyntaxKind.AsteriskToken); + } + N(SyntaxKind.AsteriskToken); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method14() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) int[]> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) int[]> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "int").WithArguments(",").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.ArrayType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.ArrayRankSpecifier); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.OmittedArraySizeExpression); + { + N(SyntaxKind.OmittedArraySizeExpressionToken); + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method15() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static IDictionary<(string Value, string Description) int*[]> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) int*[]> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "int").WithArguments(",").WithLocation(1, 55)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.ArrayType); + { + N(SyntaxKind.PointerType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.AsteriskToken); + } + N(SyntaxKind.ArrayRankSpecifier); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.OmittedArraySizeExpression); + { + N(SyntaxKind.OmittedArraySizeExpressionToken); + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingCommaIncludingAngleBracket_Method16() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + // We actually parse + // static IDictionary<(string Value, string Description), int> GetAllValues(Type t); + // and entirely ignore the ref, but provide the ',' expected error in both places + const string source = + """ + static IDictionary<(string Value, string Description) ref int> GetAllValues(Type t); + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) ref int> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "ref").WithArguments(",").WithLocation(1, 55), + // (1,59): error CS1003: Syntax error, ',' expected + // static IDictionary<(string Value, string Description) ref int> GetAllValues(Type t); + Diagnostic(ErrorCode.ERR_SyntaxError, "int").WithArguments(",").WithLocation(1, 59)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "GetAllValues"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Type"); + } + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_GenericDelegateAssignment01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static void Method' expected + // static void Method").WithLocation(1, 21), + // (3,14): error CS1003: Syntax error, '>' expected + // Action").WithLocation(3, 14)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "t"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_GenericDelegateAssignment02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static void Method' expected + // static void Method").WithLocation(1, 21), + // (3,29): error CS1003: Syntax error, '>' expected + // Action").WithLocation(3, 29), + // (3,29): error CS1003: Syntax error, '>' expected + // Action").WithLocation(3, 29)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "t"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + } + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_GenericDelegateAssignment03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static void Method)Method, (Action)Method' expected + // static void Method").WithLocation(1, 21)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.DeclarationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "var"); + } + N(SyntaxKind.ParenthesizedVariableDesignation); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "u"); + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_GenericDelegateAssignment04() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static void Method)Method, (Action>)Method' expected + // static void Method").WithLocation(1, 21)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.DeclarationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "var"); + } + N(SyntaxKind.ParenthesizedVariableDesignation); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "u"); + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + } + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_GenericDelegateAssignment05() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static void Method' expected + // static void Method").WithLocation(1, 24), + // (3,17): error CS1003: Syntax error, '>' expected + // Action").WithLocation(3, 17)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "U"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "U"); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "t"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "U"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_GenericDelegateAssignment06() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static void Method' expected + // static void Method").WithLocation(1, 24), + // (3,32): error CS1003: Syntax error, '>' expected + // Action").WithLocation(3, 32), + // (3,32): error CS1003: Syntax error, '>' expected + // Action").WithLocation(3, 32)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "U"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "U"); + } + M(SyntaxKind.GreaterThanToken); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "t"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + } + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "U"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_GenericDelegateAssignment07() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static void Method)Method, (Action)Method' expected + // static void Method").WithLocation(1, 24)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "U"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.DeclarationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "var"); + } + N(SyntaxKind.ParenthesizedVariableDesignation); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "u"); + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "U"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "U"); + } + N(SyntaxKind.GreaterThanToken); + } + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "U"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "U"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_GenericDelegateAssignment08() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + static void Method)Method, (Action>)Method' expected + // static void Method").WithLocation(1, 24)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "U"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.DeclarationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "var"); + } + N(SyntaxKind.ParenthesizedVariableDesignation); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "t"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "u"); + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "U"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "U"); + } + N(SyntaxKind.GreaterThanToken); + } + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Action"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "U"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + } + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "U"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Property01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + IEnumerable<(string Value, string Description) Values { get; set; } + """; + + UsingDeclaration(source, options, + // (1,48): error CS1003: Syntax error, '>' expected + // IEnumerable<(string Value, string Description) Values { get; set; } + Diagnostic(ErrorCode.ERR_SyntaxError, "Values").WithArguments(">").WithLocation(1, 48)); + + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "Values"); + N(SyntaxKind.AccessorList); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.SetAccessorDeclaration); + { + N(SyntaxKind.SetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Property02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + IEnumerable<(string Value, string Description) Values => null; + """; + + UsingDeclaration(source, options, + // (1,48): error CS1003: Syntax error, '>' expected + // IEnumerable<(string Value, string Description) Values => null; + Diagnostic(ErrorCode.ERR_SyntaxError, "Values").WithArguments(">").WithLocation(1, 48)); + + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "Values"); + N(SyntaxKind.ArrowExpressionClause); + { + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Property03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + Dictionary' expected + // Dictionary").WithLocation(1, 24)); + + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Dictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "Values"); + N(SyntaxKind.AccessorList); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.SetAccessorDeclaration); + { + N(SyntaxKind.SetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Property04() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + IEnumerable<(string Value, string Description Values { get; set; } + """; + + UsingDeclaration(source, options, + // (1,47): error CS1026: ) expected + // IEnumerable<(string Value, string Description Values { get; set; } + Diagnostic(ErrorCode.ERR_CloseParenExpected, "Values").WithLocation(1, 47), + // (1,47): error CS1003: Syntax error, '>' expected + // IEnumerable<(string Value, string Description Values { get; set; } + Diagnostic(ErrorCode.ERR_SyntaxError, "Values").WithArguments(">").WithLocation(1, 47)); + + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + M(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "Values"); + N(SyntaxKind.AccessorList); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.SetAccessorDeclaration); + { + N(SyntaxKind.SetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Property05() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + IEnumerable<(string Value, string Description Values => null; + """; + + UsingDeclaration(source, options, + // (1,47): error CS1026: ) expected + // IEnumerable<(string Value, string Description Values => null; + Diagnostic(ErrorCode.ERR_CloseParenExpected, "Values").WithLocation(1, 47), + // (1,47): error CS1003: Syntax error, '>' expected + // IEnumerable<(string Value, string Description Values => null; + Diagnostic(ErrorCode.ERR_SyntaxError, "Values").WithArguments(">").WithLocation(1, 47)); + + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + M(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "Values"); + N(SyntaxKind.ArrowExpressionClause); + { + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Local01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + IEnumerable<(string Value, string Description) values; + """; + + UsingStatement(source, options, + // (1,48): error CS1003: Syntax error, '>' expected + // IEnumerable<(string Value, string Description) values; + Diagnostic(ErrorCode.ERR_SyntaxError, "values").WithArguments(">").WithLocation(1, 48)); + + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "values"); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Local02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + IEnumerable<(string Value, string Description) values = null; + """; + + UsingStatement(source, options, + // (1,48): error CS1003: Syntax error, '>' expected + // IEnumerable<(string Value, string Description) values = null; + Diagnostic(ErrorCode.ERR_SyntaxError, "values").WithArguments(">").WithLocation(1, 48)); + + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "values"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Local03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + IEnumerable<(string Value, string Description) + values = null, + otherValues, + moreValues + ; + """; + + UsingStatement(source, options, + // (1,47): error CS1003: Syntax error, '>' expected + // IEnumerable<(string Value, string Description) + Diagnostic(ErrorCode.ERR_SyntaxError, "").WithArguments(">").WithLocation(1, 47)); + + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "values"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "otherValues"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "moreValues"); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Field01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + IEnumerable<(string Value, string Description) values; + """; + + UsingDeclaration(source, options, + // (1,48): error CS1003: Syntax error, '>' expected + // IEnumerable<(string Value, string Description) values; + Diagnostic(ErrorCode.ERR_SyntaxError, "values").WithArguments(">").WithLocation(1, 48) + ); + + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "values"); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Field02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + IEnumerable<(string Value, string Description) values = null; + """; + + UsingDeclaration(source, options, + // (1,48): error CS1003: Syntax error, '>' expected + // IEnumerable<(string Value, string Description) values = null; + Diagnostic(ErrorCode.ERR_SyntaxError, "values").WithArguments(">").WithLocation(1, 48)); + + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "values"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(24642, "https://github.com/dotnet/roslyn/issues/24642")] + public void MissingClosingAngleBracket_Field03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + IEnumerable<(string Value, string Description) + values = null, + otherValues, + moreValues + ; + """; + + UsingDeclaration(source, options, + // (1,47): error CS1003: Syntax error, '>' expected + // IEnumerable<(string Value, string Description) + Diagnostic(ErrorCode.ERR_SyntaxError, "").WithArguments(">").WithLocation(1, 47)); + + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "values"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "otherValues"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "moreValues"); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_MethodArgumentList01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public void M(ImmutableArray' expected + // public void M(ImmutableArray").WithLocation(1, 34)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "arr"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_MethodArgumentList02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public void M(ImmutableArray another); + """; + + UsingDeclaration(source, options, + // (1,34): error CS1003: Syntax error, ',' expected + // public void M(ImmutableArray another); + Diagnostic(ErrorCode.ERR_SyntaxError, "arr").WithArguments(",").WithLocation(1, 34), + // (1,59): error CS1003: Syntax error, '>' expected + // public void M(ImmutableArray another); + Diagnostic(ErrorCode.ERR_SyntaxError, "another").WithArguments(">").WithLocation(1, 59)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "arr"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "another"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_MethodArgumentList03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public ImmutableArray' expected + // public ImmutableArray").WithLocation(1, 27), + // (1,48): error CS1003: Syntax error, ',' expected + // public ImmutableArray' expected + // public ImmutableArray").WithLocation(1, 70), + // (1,70): error CS1003: Syntax error, '>' expected + // public ImmutableArray").WithLocation(1, 70)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_MethodArgumentList04() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public ImmutableArray' expected + // public ImmutableArray").WithLocation(1, 25), + // (1,28): error CS1003: Syntax error, '>' expected + // public ImmutableArray").WithLocation(1, 28), + // (1,46): error CS1003: Syntax error, ',' expected + // public ImmutableArray' expected + // public ImmutableArray").WithLocation(1, 66), + // (1,66): error CS1003: Syntax error, '>' expected + // public ImmutableArray").WithLocation(1, 66)); + + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_MethodInvocation01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + Invoke' expected + // Invoke").WithLocation(1, 43), + // (1,74): error CS1003: Syntax error, '>' expected + // Invoke").WithLocation(1, 74)); + + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Invoke"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "31"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.DefaultExpression); + { + N(SyntaxKind.DefaultKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_MethodInvocation02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + Invoke(31, default(ImmutableArray' expected + // Invoke(31, default(ImmutableArray").WithLocation(1, 75)); + + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Invoke"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "31"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.DefaultExpression); + { + N(SyntaxKind.DefaultKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_MethodInvocation03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + Invoke(31, default(ImmutableArray)); + """; + + UsingStatement(source, options); + + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Invoke"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "31"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.DefaultExpression); + { + N(SyntaxKind.DefaultKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_RecordPrimaryConstructorArgumentList01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public record M(ImmutableArray' expected + // public record M(ImmutableArray").WithLocation(1, 36)); + + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "Array"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_RecordPrimaryConstructorArgumentList02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public record M' expected + // public record M").WithLocation(1, 18), + // (1,36): error CS1003: Syntax error, '>' expected + // public record M").WithLocation(1, 36)); + + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "ImmutableArray"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "Array"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_RecordPrimaryConstructorArgumentList03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public record M' expected + // public record M").WithLocation(1, 18), + // (1,36): error CS1003: Syntax error, '>' expected + // public record M").WithLocation(1, 36), + // (2,14): error CS1003: Syntax error, '>' expected + // : Other").WithLocation(2, 14), + // (2,33): error CS1003: Syntax error, ',' expected + // : Other' expected + // public IEnumerable<(string Value, string Description) this[string index] { get; } + Diagnostic(ErrorCode.ERR_SyntaxError, "this").WithArguments(">").WithLocation(1, 55)); + + N(SyntaxKind.IndexerDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.ThisKeyword); + N(SyntaxKind.BracketedParameterList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "index"); + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.AccessorList); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_ThisAccessor02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public IEnumerable<(string Value, string Description) this[IDictionary' expected + // public IEnumerable<(string Value, string Description) this[IDictionary").WithLocation(1, 55), + // (1,84): error CS1003: Syntax error, '>' expected + // public IEnumerable<(string Value, string Description) this[IDictionary").WithLocation(1, 84)); + + N(SyntaxKind.IndexerDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.ThisKeyword); + N(SyntaxKind.BracketedParameterList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "keys"); + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.AccessorList); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_ThisAccessor03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public IEnumerable<(string Value, string Description) this[IDictionary null; + """; + + UsingDeclaration(source, options, + // (1,55): error CS1003: Syntax error, '>' expected + // public IEnumerable<(string Value, string Description) this[IDictionary null; + Diagnostic(ErrorCode.ERR_SyntaxError, "this").WithArguments(">").WithLocation(1, 55), + // (1,84): error CS1003: Syntax error, '>' expected + // public IEnumerable<(string Value, string Description) this[IDictionary null; + Diagnostic(ErrorCode.ERR_SyntaxError, "keys").WithArguments(">").WithLocation(1, 84)); + + N(SyntaxKind.IndexerDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.ThisKeyword); + N(SyntaxKind.BracketedParameterList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IDictionary"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "keys"); + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ArrowExpressionClause); + { + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_Operator01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public static IEnumerable<(string Value, string Description) operator +(X left, X right); + """; + + UsingDeclaration(source, options, + // (1,62): error CS1003: Syntax error, '>' expected + // public static IEnumerable<(string Value, string Description) operator +(X left, X right); + Diagnostic(ErrorCode.ERR_SyntaxError, "operator").WithArguments(">").WithLocation(1, 62)); + + N(SyntaxKind.OperatorDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.PlusToken); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.IdentifierToken, "left"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.IdentifierToken, "right"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_Operator02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public static IEnumerable<(string Value, string Description) operator checked +(X left, X right); + """; + + UsingDeclaration(source, options, + // (1,62): error CS1003: Syntax error, '>' expected + // public static IEnumerable<(string Value, string Description) operator checked +(X left, X right); + Diagnostic(ErrorCode.ERR_SyntaxError, "operator").WithArguments(">").WithLocation(1, 62)); + + N(SyntaxKind.OperatorDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.CheckedKeyword); + N(SyntaxKind.PlusToken); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.IdentifierToken, "left"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.IdentifierToken, "right"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_ConversionOperator01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public static implicit operator IEnumerable<(string Value, string Description)(X source); + """; + + UsingDeclaration(source, options, + // (1,79): error CS1003: Syntax error, '>' expected + // public static implicit operator IEnumerable<(string Value, string Description)(X source); + Diagnostic(ErrorCode.ERR_SyntaxError, "(").WithArguments(">").WithLocation(1, 79)); + + N(SyntaxKind.ConversionOperatorDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.ImplicitKeyword); + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TupleType); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Value"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TupleElement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Description"); + } + N(SyntaxKind.CloseParenToken); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.IdentifierToken, "source"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(48566, "https://github.com/dotnet/roslyn/issues/48566")] + public void MissingClosingAngleBracket_ConversionOperator02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + const string source = + """ + public static implicit operator IEnumerable' expected + // public static implicit operator IEnumerable").WithLocation(1, 51)); + + N(SyntaxKind.ConversionOperatorDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.ImplicitKeyword); + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "IEnumerable"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + M(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.IdentifierToken, "source"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + #endregion } } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests2.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests2.cs index 51fd79f19312e..ddc8174b9dbe3 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests2.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests2.cs @@ -945,5 +945,421 @@ public void ExtendedPropertySubpattern_InPositionalPattern() } EOF(); } + + #region Missing > in type parameter list + + [Fact] + public void MissingClosingAngleBracket01() + { + UsingExpression(@"e is List' expected + // e is List").WithLocation(1, 15), + // (1,30): error CS1525: Invalid expression term 'int' + // e is List' expected + // e is not List").WithLocation(1, 19), + // (1,39): error CS1525: Invalid expression term 'int' + // e is not List' expected + // e is (not List").WithLocation(1, 20), + // (1,40): error CS1525: Invalid expression term 'int' + // e is (not List' expected + // e is (not List").WithLocation(1, 57), + // (1,77): error CS1525: Invalid expression term 'int' + // e is (not List' expected + // e is A.B").WithLocation(1, 12)); + + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IsPatternExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.IsKeyword); + N(SyntaxKind.OrPattern); + { + N(SyntaxKind.TypePattern); + { + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "B"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + } + N(SyntaxKind.OrKeyword); + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "D"); + } + } + } + } + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Y"); + } + } + EOF(); + } + + #endregion } } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ScriptParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ScriptParsingTests.cs index 17d12a4d447e1..18a55b0dd7804 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ScriptParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ScriptParsingTests.cs @@ -9236,38 +9236,34 @@ public void GlobalStatementSeparators_Comma2() a < b, void goo() { } ", - // (2,6): error CS1002: ; expected - // a < b, - Diagnostic(ErrorCode.ERR_SemicolonExpected, ",").WithLocation(2, 6), - // (2,6): error CS7017: Member definition, statement, or end-of-file expected - // a < b, - Diagnostic(ErrorCode.ERR_GlobalDefinitionOrStatementExpected, ",").WithLocation(2, 6)); + // (3,1): error CS1547: Keyword 'void' cannot be used in this context + // void goo() { } + Diagnostic(ErrorCode.ERR_NoVoidHere, "void").WithLocation(3, 1), + // (3,6): error CS1003: Syntax error, '>' expected + // void goo() { } + Diagnostic(ErrorCode.ERR_SyntaxError, "goo").WithArguments(">").WithLocation(3, 6)); + N(SyntaxKind.CompilationUnit); { - N(SyntaxKind.GlobalStatement); + N(SyntaxKind.MethodDeclaration); { - N(SyntaxKind.ExpressionStatement); + N(SyntaxKind.GenericName); { - N(SyntaxKind.LessThanExpression); + N(SyntaxKind.IdentifierToken, "a"); + N(SyntaxKind.TypeArgumentList); { - N(SyntaxKind.IdentifierName); - { - N(SyntaxKind.IdentifierToken, "a"); - } N(SyntaxKind.LessThanToken); N(SyntaxKind.IdentifierName); { N(SyntaxKind.IdentifierToken, "b"); } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + M(SyntaxKind.GreaterThanToken); } - M(SyntaxKind.SemicolonToken); - } - } - N(SyntaxKind.MethodDeclaration); - { - N(SyntaxKind.PredefinedType); - { - N(SyntaxKind.VoidKeyword); } N(SyntaxKind.IdentifierToken, "goo"); N(SyntaxKind.ParameterList); diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs index 00f2a1223c791..4094cc28447d9 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs @@ -5,6 +5,7 @@ #nullable disable using System; +using System.Linq; using Roslyn.Test.Utilities; using Xunit; @@ -1284,5 +1285,61 @@ void M() VerifyNotEquivalent(tree1, tree2, topLevel: false); VerifyEquivalent(tree1, tree2, topLevel: true); } + + [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2018744")] + public void TestDeeplyNested1() + { + var expr = string.Join(" + ", Enumerable.Range(0, 10000).Select(_ => "a")); + + var tree1 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a, int b, int c) + { + var v = {{expr}} + b; + } + } + """"); + + var tree2 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a, int b, int c) + { + var v = {{expr}} + c; + } + } + """"); + + VerifyNotEquivalent(tree1, tree2, topLevel: false); + } + + [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2018744")] + public void TestDeeplyNested2() + { + var expr = string.Join(" + ", Enumerable.Range(0, 10000).Select(_ => "a")); + + var tree1 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a) + { + var v = {{expr}}; + } + } + """"); + + var tree2 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a) + { + var v = {{expr}}; + } + } + """"); + + VerifyEquivalent(tree1, tree2, topLevel: false); + } } } diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs index f8f0d20bfe449..b9d54c3d01f96 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs @@ -5,6 +5,9 @@ #nullable disable using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -276,5 +279,68 @@ public void IsAttributeTargetSpecifier() } } } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72300")] + public void TestAllKindsReturnedFromGetKindsMethodsExist() + { + foreach (var method in typeof(SyntaxFacts).GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (method.ReturnType == typeof(IEnumerable) && method.GetParameters() is []) + { + foreach (var kind in (IEnumerable)method.Invoke(null, null)) + { + Assert.True(Enum.IsDefined(typeof(SyntaxKind), kind), $"Nonexistent kind '{kind}' returned from method '{method.Name}'"); + } + } + } + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72300")] + [InlineData(nameof(SyntaxFacts.GetContextualKeywordKinds), SyntaxKind.YieldKeyword, SyntaxKind.ElifKeyword)] + [InlineData(nameof(SyntaxFacts.GetPunctuationKinds), SyntaxKind.TildeToken, SyntaxKind.BoolKeyword)] + [InlineData(nameof(SyntaxFacts.GetReservedKeywordKinds), SyntaxKind.BoolKeyword, SyntaxKind.YieldKeyword)] + public void TestRangeBasedGetKindsMethodsReturnExpectedResults(string methodName, SyntaxKind lowerBoundInclusive, SyntaxKind upperBoundExclusive) + { + var method = typeof(SyntaxFacts).GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); + + Assert.NotNull(method); + Assert.Equal(0, method.GetParameters().Length); + Assert.Equal(typeof(IEnumerable), method.ReturnType); + + var returnedKindsInts = ((IEnumerable)method.Invoke(null, null)).Select(static k => (int)k).ToHashSet(); + + for (int i = (int)lowerBoundInclusive; i < (int)upperBoundExclusive; i++) + { + if (Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)) + { + Assert.True(returnedKindsInts.Remove(i)); + } + else + { + Assert.DoesNotContain(i, returnedKindsInts); + } + } + + // We've already removed all expected kinds from the set. It should be empty now + Assert.Empty(returnedKindsInts); + } + + [Fact] + public void TestGetPreprocessorKeywordKindsReturnsExpectedResults() + { + var returnedKindsInts = SyntaxFacts.GetPreprocessorKeywordKinds().Select(static k => (int)k).ToHashSet(); + + Assert.True(returnedKindsInts.Remove((int)SyntaxKind.TrueKeyword)); + Assert.True(returnedKindsInts.Remove((int)SyntaxKind.FalseKeyword)); + Assert.True(returnedKindsInts.Remove((int)SyntaxKind.DefaultKeyword)); + + for (int i = (int)SyntaxKind.ElifKeyword; i < (int)SyntaxKind.ReferenceKeyword; i++) + { + Assert.True(returnedKindsInts.Remove(i)); + } + + // We've already removed all expected kinds from the set. It should be empty now + Assert.Empty(returnedKindsInts); + } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs index d309e6fad88f4..1d344104efe9f 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs @@ -932,6 +932,33 @@ public void EditorConfigToDiagnostics() }, options.Select(o => o.TreeOptions).ToArray()); } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72657")] + [InlineData("/", "/")] + [InlineData("/a/b/c/", "/a/b/c/")] + [InlineData("/a/b//c/", "/a/b/c/")] + [InlineData("/a/b/c/", "/a/b//c/")] + [InlineData("/a/b//c/", "/a/b//c/")] + [InlineData("/a/b/c//", "/a/b/c/")] + [InlineData("/a/b/c/", "/a/b/c//")] + [InlineData("/a/b/c//", "/a/b/c//")] + [InlineData("/a/b//c/", "/a/b///c/")] + public void EditorConfigToDiagnostics_DoubleSlash(string prefixEditorConfig, string prefixSource) + { + var configs = ArrayBuilder.GetInstance(); + configs.Add(Parse(""" + [*.cs] + dotnet_diagnostic.cs000.severity = none + """, + prefixEditorConfig + ".editorconfig")); + + var options = GetAnalyzerConfigOptions([prefixSource + "test.cs"], configs); + configs.Free(); + + Assert.Equal([ + CreateImmutableDictionary(("cs000", ReportDiagnostic.Suppress)) + ], options.Select(o => o.TreeOptions).ToArray()); + } + [Fact] public void LaterSectionOverrides() { @@ -1083,6 +1110,28 @@ public void MultipleEditorConfigs() }, options.Select(o => o.TreeOptions).ToArray()); } + [Fact] + public void FolderNamePrefixOfFileName() + { + var configs = ArrayBuilder.GetInstance(); + configs.Add(Parse(@" +[*.cs] +dotnet_diagnostic.cs000.severity = suggestion", "/root/.editorconfig")); + configs.Add(Parse(@" +root=true", "/root/test/.editorconfig")); + + var options = GetAnalyzerConfigOptions( + new[] { "/root/testing.cs" }, + configs); + configs.Free(); + + Assert.Equal(new[] + { + CreateImmutableDictionary( + ("cs000", ReportDiagnostic.Info)), + }, options.Select(o => o.TreeOptions).ToArray()); + } + [Fact] public void InheritOuterConfig() { diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceAppDomainTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceAppDomainTests.cs index 7d679f73ad6e9..f79eaa292b2c4 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceAppDomainTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceAppDomainTests.cs @@ -94,9 +94,9 @@ public class TestAnalyzer : DiagnosticAnalyzer new SyntaxTree[] { CSharp.SyntaxFactory.ParseSyntaxTree(analyzerSource) }, new MetadataReference[] { - NetStandard20.mscorlib, - NetStandard20.netstandard, - NetStandard20.SystemRuntime, + NetStandard20.References.mscorlib, + NetStandard20.References.netstandard, + NetStandard20.References.SystemRuntime, MetadataReference.CreateFromFile(immutable.Path), MetadataReference.CreateFromFile(analyzer.Path) }, diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/ArrayBuilderTests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/ArrayBuilderTests.cs index 29a4983f3362d..79716c5880351 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/ArrayBuilderTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/ArrayBuilderTests.cs @@ -13,6 +13,30 @@ namespace Microsoft.CodeAnalysis.UnitTests.Collections { public class ArrayBuilderTests { + [Fact] + public void RemoveAll() + { + var builder = new ArrayBuilder { 6, 5, 1, 2, 3, 2, 4, 5, 1, 7 }; + + builder.RemoveAll((_, _) => false, arg: 0); + AssertEx.Equal([6, 5, 1, 2, 3, 2, 4, 5, 1, 7], builder); + + builder.RemoveAll((i, arg) => i == arg, arg: 6); + AssertEx.Equal([5, 1, 2, 3, 2, 4, 5, 1, 7], builder); + + builder.RemoveAll((i, arg) => i == arg, arg: 7); + AssertEx.Equal([5, 1, 2, 3, 2, 4, 5, 1], builder); + + builder.RemoveAll((i, arg) => i < arg, arg: 3); + AssertEx.Equal([5, 3, 4, 5], builder); + + builder.RemoveAll((_, _) => true, arg: 0); + AssertEx.Equal([], builder); + + builder.RemoveAll((_, _) => true, arg: 0); + AssertEx.Equal([], builder); + } + [Fact] public void RemoveDuplicates1() { diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/ISet_Generic_Tests`1.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/ISet_Generic_Tests`1.cs index a65d2f96bc642..756c33f591bdd 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/ISet_Generic_Tests`1.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/ISet_Generic_Tests`1.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.7/src/libraries/Common/tests/System/Collections/ISet.Generic.Tests.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/ISet.Generic.Tests.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -312,38 +312,58 @@ private void Validate_UnionWith(ISet set, IEnumerable enumerable) public void ISet_Generic_NullEnumerableArgument(int count) { ISet set = GenericISetFactory(count); - Assert.Throws(() => set.ExceptWith(null!)); - Assert.Throws(() => set.IntersectWith(null!)); Assert.Throws(() => set.IsProperSubsetOf(null!)); Assert.Throws(() => set.IsProperSupersetOf(null!)); Assert.Throws(() => set.IsSubsetOf(null!)); Assert.Throws(() => set.IsSupersetOf(null!)); Assert.Throws(() => set.Overlaps(null!)); Assert.Throws(() => set.SetEquals(null!)); - Assert.Throws(() => set.SymmetricExceptWith(null!)); - Assert.Throws(() => set.UnionWith(null!)); + if (!IsReadOnly) + { + Assert.Throws(() => set.ExceptWith(null!)); + Assert.Throws(() => set.IntersectWith(null!)); + Assert.Throws(() => set.SymmetricExceptWith(null!)); + Assert.Throws(() => set.UnionWith(null!)); + } + else + { + Assert.Throws(() => set.Add(CreateT(0))); + Assert.Throws(() => set.ExceptWith(null!)); + Assert.Throws(() => set.IntersectWith(null!)); + Assert.Throws(() => set.SymmetricExceptWith(null!)); + Assert.Throws(() => set.UnionWith(null!)); + } } + public static IEnumerable SetTestData() => + new[] { EnumerableType.SegmentedHashSet, EnumerableType.List }.SelectMany(GetEnumerableTestData); + [Theory] [MemberData(nameof(EnumerableTestData))] public void ISet_Generic_ExceptWith(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { - ISet set = GenericISetFactory(setLength); - IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); - Validate_ExceptWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); + Validate_ExceptWith(set, enumerable); + } } [Theory] [MemberData(nameof(EnumerableTestData))] public void ISet_Generic_IntersectWith(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { - ISet set = GenericISetFactory(setLength); - IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); - Validate_IntersectWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); + Validate_IntersectWith(set, enumerable); + } } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_IsProperSubsetOf(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -352,7 +372,7 @@ public void ISet_Generic_IsProperSubsetOf(EnumerableType enumerableType, int set } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_IsProperSupersetOf(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -361,7 +381,7 @@ public void ISet_Generic_IsProperSupersetOf(EnumerableType enumerableType, int s } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_IsSubsetOf(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -370,7 +390,7 @@ public void ISet_Generic_IsSubsetOf(EnumerableType enumerableType, int setLength } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_IsSupersetOf(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -379,7 +399,7 @@ public void ISet_Generic_IsSupersetOf(EnumerableType enumerableType, int setLeng } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_Overlaps(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -388,7 +408,7 @@ public void ISet_Generic_Overlaps(EnumerableType enumerableType, int setLength, } [Theory] - [MemberData(nameof(EnumerableTestData))] + [MemberData(nameof(SetTestData))] public void ISet_Generic_SetEquals(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { ISet set = GenericISetFactory(setLength); @@ -400,18 +420,24 @@ public void ISet_Generic_SetEquals(EnumerableType enumerableType, int setLength, [MemberData(nameof(EnumerableTestData))] public void ISet_Generic_SymmetricExceptWith(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { - ISet set = GenericISetFactory(setLength); - IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); - Validate_SymmetricExceptWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); + Validate_SymmetricExceptWith(set, enumerable); + } } [Theory] [MemberData(nameof(EnumerableTestData))] public void ISet_Generic_UnionWith(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { - ISet set = GenericISetFactory(setLength); - IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); - Validate_UnionWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); + Validate_UnionWith(set, enumerable); + } } #endregion @@ -422,16 +448,22 @@ public void ISet_Generic_UnionWith(EnumerableType enumerableType, int setLength, [MemberData(nameof(ValidCollectionSizes))] public void ISet_Generic_ExceptWith_Itself(int setLength) { - ISet set = GenericISetFactory(setLength); - Validate_ExceptWith(set, set); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + Validate_ExceptWith(set, set); + } } [ConditionalTheory(typeof(CoreClrOnly))] [MemberData(nameof(ValidCollectionSizes))] public void ISet_Generic_IntersectWith_Itself(int setLength) { - ISet set = GenericISetFactory(setLength); - Validate_IntersectWith(set, set); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + Validate_IntersectWith(set, set); + } } [Theory] @@ -486,16 +518,22 @@ public void ISet_Generic_SetEquals_Itself(int setLength) [MemberData(nameof(ValidCollectionSizes))] public void ISet_Generic_SymmetricExceptWith_Itself(int setLength) { - ISet set = GenericISetFactory(setLength); - Validate_SymmetricExceptWith(set, set); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + Validate_SymmetricExceptWith(set, set); + } } [Theory] [MemberData(nameof(ValidCollectionSizes))] public void ISet_Generic_UnionWith_Itself(int setLength) { - ISet set = GenericISetFactory(setLength); - Validate_UnionWith(set, set); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + Validate_UnionWith(set, set); + } } #endregion @@ -505,17 +543,23 @@ public void ISet_Generic_UnionWith_Itself(int setLength) [Fact] public void ISet_Generic_ExceptWith_LargeSet() { - ISet set = GenericISetFactory(ISet_Large_Capacity); - IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); - Validate_ExceptWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(ISet_Large_Capacity); + IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); + Validate_ExceptWith(set, enumerable); + } } [Fact] public void ISet_Generic_IntersectWith_LargeSet() { - ISet set = GenericISetFactory(ISet_Large_Capacity); - IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); - Validate_IntersectWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(ISet_Large_Capacity); + IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); + Validate_IntersectWith(set, enumerable); + } } [Fact] @@ -569,17 +613,23 @@ public void ISet_Generic_SetEquals_LargeSet() [Fact] public void ISet_Generic_SymmetricExceptWith_LargeSet() { - ISet set = GenericISetFactory(ISet_Large_Capacity); - IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); - Validate_SymmetricExceptWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(ISet_Large_Capacity); + IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); + Validate_SymmetricExceptWith(set, enumerable); + } } [Fact] public void ISet_Generic_UnionWith_LargeSet() { - ISet set = GenericISetFactory(ISet_Large_Capacity); - IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); - Validate_UnionWith(set, enumerable); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(ISet_Large_Capacity); + IEnumerable enumerable = CreateEnumerable(EnumerableType.List, set, 150, 0, 0); + Validate_UnionWith(set, enumerable); + } } #endregion @@ -590,24 +640,27 @@ public void ISet_Generic_UnionWith_LargeSet() [MemberData(nameof(EnumerableTestData))] public void ISet_Generic_SymmetricExceptWith_AfterRemovingElements(EnumerableType enumerableType, int setLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) { - ISet set = GenericISetFactory(setLength); - T value = CreateT(532); - set.Add(value); - set.Remove(value); - IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); - Debug.Assert(enumerable != null); + if (!IsReadOnly) + { + ISet set = GenericISetFactory(setLength); + T value = CreateT(532); + set.Add(value); + set.Remove(value); + IEnumerable enumerable = CreateEnumerable(enumerableType, set, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements); + Debug.Assert(enumerable != null); - IEqualityComparer comparer = GetIEqualityComparer(); - HashSet expected = new HashSet(comparer); - foreach (T element in enumerable) - if (!set.Contains(element, comparer)) - expected.Add(element); - foreach (T element in set) - if (!enumerable.Contains(element, comparer)) - expected.Add(element); - set.SymmetricExceptWith(enumerable); - Assert.Equal(expected.Count, set.Count); - Assert.True(expected.SetEquals(set)); + IEqualityComparer comparer = GetIEqualityComparer(); + HashSet expected = new HashSet(comparer); + foreach (T element in enumerable) + if (!set.Contains(element, comparer)) + expected.Add(element); + foreach (T element in set) + if (!enumerable.Contains(element, comparer)) + expected.Add(element); + set.SymmetricExceptWith(enumerable); + Assert.Equal(expected.Count, set.Count); + Assert.True(expected.SetEquals(set)); + } } #endregion diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_Generic_Tests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_Generic_Tests.cs index 3068f0fff5e8a..f34b6cfa1d944 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_Generic_Tests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_Generic_Tests.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.7/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_Generic_Tests`1.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_Generic_Tests`1.cs index db779881c560d..76ab9a0479d0f 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_Generic_Tests`1.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_Generic_Tests`1.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.7/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -24,6 +24,8 @@ public abstract class SegmentedHashSet_Generic_Tests : ISet_Generic_Tests where T : notnull { #region ISet Helper Methods + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; protected override bool ResetImplemented => true; diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_IEnumerable_NonGeneric_Tests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_IEnumerable_NonGeneric_Tests.cs index 0eb9c23b473a9..4bf8a192de1ae 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_IEnumerable_NonGeneric_Tests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/SegmentedHashSet_IEnumerable_NonGeneric_Tests.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.7/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.AsNonGenericIEnumerable.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.AsNonGenericIEnumerable.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -26,6 +26,7 @@ protected override IEnumerable NonGenericIEnumerableFactory(int count) return set; } + protected override bool Enumerator_Empty_UsesSingletonInstance => true; protected override bool Enumerator_Current_UndefinedOperation_Throws => true; protected override ModifyOperation ModifyEnumeratorThrows => base.ModifyEnumeratorAllowed & ~ModifyOperation.Remove; diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/TestingTypes.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/TestingTypes.cs index e7a15663a60f2..82d7e1cc07f5b 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/TestingTypes.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/HashSet/TestingTypes.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.7/src/libraries/Common/tests/System/Collections/TestingTypes.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/TestingTypes.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -396,5 +396,16 @@ public int GetHashCode(T obj) } } + public sealed class EqualityComparerConstantHashCode : IEqualityComparer + { + private readonly IEqualityComparer _comparer; + + public EqualityComparerConstantHashCode(IEqualityComparer comparer) => _comparer = comparer; + + public bool Equals(T? x, T? y) => _comparer.Equals(x, y); + + public int GetHashCode(T? obj) => 42; + } + #endregion } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs index 994564ca25cae..537dd761567aa 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs @@ -147,13 +147,26 @@ public void Single() Func isOdd = x => x % 2 == 1; - // BUG:753260 Should this be ArgumentNullException for consistency? Assert.Throws(() => default(ImmutableArray).Single(isOdd)); Assert.Throws(() => ImmutableArray.Create().Single(isOdd)); Assert.Equal(1, ImmutableArray.Create(1, 2).Single(isOdd)); Assert.Throws(() => ImmutableArray.Create(1, 2, 3).Single(isOdd)); } + [Fact] + public void Single_Arg() + { + Assert.Throws(() => default(ImmutableArray).Single((_, _) => true, 1)); + Assert.Throws(() => ImmutableArray.Create().Single((x, a) => x == a, 1)); + Assert.Equal(1, ImmutableArray.Create(1).Single((x, a) => x == a, 1)); + Assert.Throws(() => ImmutableArray.Create(1, 1).Single((x, a) => x == a, 1)); + + Assert.Throws(() => default(ImmutableArray).Single((x, a) => x % a == 1, 2)); + Assert.Throws(() => ImmutableArray.Create().Single((x, a) => x % a == 1, 2)); + Assert.Equal(1, ImmutableArray.Create(1, 2).Single((x, a) => x % a == 1, 2)); + Assert.Throws(() => ImmutableArray.Create(1, 2, 3).Single((x, a) => x % a == 1, 2)); + } + [Fact] public void IndexOf() { diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/CollectionAsserts.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/CollectionAsserts.cs index d571158a472b3..82c495a1b8999 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/CollectionAsserts.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/CollectionAsserts.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/Common/tests/System/Collections/CollectionAsserts.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/CollectionAsserts.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/ICollection.Generic.Tests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/ICollection.Generic.Tests.cs index e06dfc2b3334f..3d9a90d0e0897 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/ICollection.Generic.Tests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/ICollection.Generic.Tests.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/Common/tests/System/Collections/ICollection.Generic.Tests.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/ICollection.Generic.Tests.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -11,6 +11,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests.Collections @@ -343,6 +345,46 @@ public void ICollection_Generic_Clear_Repeatedly(int count) } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ICollection_Generic_Remove_ReferenceRemovedFromCollection(bool useRemove) + { + if (typeof(T).IsValueType || IsReadOnly || AddRemoveClear_ThrowsNotSupported) + { + return; + } + + ICollection collection = GenericICollectionFactory(); + + WeakReference wr = populateAndRemove(collection, useRemove); + Assert.True(SpinWait.SpinUntil(() => + { + GC.Collect(); + return !wr.TryGetTarget(out _); + }, 30_000)); + GC.KeepAlive(collection); + + [MethodImpl(MethodImplOptions.NoInlining)] + WeakReference populateAndRemove(ICollection collection, bool useRemove) + { + AddToCollection(collection, 1); + T value = collection.First(); + + if (useRemove) + { + Assert.True(collection.Remove(value)); + } + else + { + collection.Clear(); + Assert.Equal(0, collection.Count); + } + + return new WeakReference(value); + } + } + #endregion #region Contains @@ -373,8 +415,10 @@ public void ICollection_Generic_Contains_ValidValueOnCollectionContainingThatVal public void ICollection_Generic_Contains_DefaultValueOnCollectionNotContainingDefaultValue(int count) { ICollection collection = GenericICollectionFactory(count); - if (DefaultValueAllowed) + if (DefaultValueAllowed && default(T) is null) // it's true only for reference types and for Nullable + { Assert.False(collection.Contains(default(T)!)); + } } [Theory] @@ -456,7 +500,7 @@ public void ICollection_Generic_CopyTo_IndexEqualToArrayCount_ThrowsArgumentExce ICollection collection = GenericICollectionFactory(count); T[] array = new T[count]; if (count > 0) - Assert.Throws(() => collection.CopyTo(array, count)); + Assert.ThrowsAny(() => collection.CopyTo(array, count)); else collection.CopyTo(array, count); // does nothing since the array is empty } @@ -478,7 +522,7 @@ public void ICollection_Generic_CopyTo_NotEnoughSpaceInOffsettedArray_ThrowsArgu { ICollection collection = GenericICollectionFactory(count); T[] array = new T[count]; - Assert.Throws(() => collection.CopyTo(array, 1)); + Assert.ThrowsAny(() => collection.CopyTo(array, 1)); } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/ICollection.NonGeneric.Tests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/ICollection.NonGeneric.Tests.cs index f4fdee97049f0..f511731333c7d 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/ICollection.NonGeneric.Tests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/ICollection.NonGeneric.Tests.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/Common/tests/System/Collections/ICollection.NonGeneric.Tests.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/ICollection.NonGeneric.Tests.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -163,7 +163,10 @@ public void ICollection_NonGeneric_SyncRootUnique(int count) { ICollection collection1 = NonGenericICollectionFactory(count); ICollection collection2 = NonGenericICollectionFactory(count); - Assert.NotSame(collection1.SyncRoot, collection2.SyncRoot); + if (!ReferenceEquals(collection1, collection2)) + { + Assert.NotSame(collection1.SyncRoot, collection2.SyncRoot); + } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.Generic.Tests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.Generic.Tests.cs index 580da72026389..63b1cc00bafbe 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.Generic.Tests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.Generic.Tests.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Tests.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Tests.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -73,9 +73,16 @@ public abstract partial class IEnumerable_Generic_Tests : TestBase protected virtual bool Enumerator_Current_UndefinedOperation_Throws => false; /// - /// Same as but only on empty collections. + /// When calling Current of the empty enumerator before the first MoveNext, after the end of the collection, + /// or after modification of the enumeration, the resulting behavior is undefined. Tests are included + /// to cover two behavioral scenarios: + /// - Throwing an InvalidOperationException + /// - Returning an undefined value. + /// + /// If this property is set to true, the tests ensure that the exception is thrown. The default value is + /// . /// - protected virtual bool Enumerator_Current_UndefinedOperation_Throws_On_Empty => false; + protected virtual bool Enumerator_Empty_Current_UndefinedOperation_Throws => Enumerator_Current_UndefinedOperation_Throws; /// /// When calling MoveNext or Reset after modification of the enumeration, the resulting behavior is @@ -88,6 +95,20 @@ public abstract partial class IEnumerable_Generic_Tests : TestBase /// protected virtual bool Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException => true; + /// + /// When calling MoveNext or Reset after modification of an empty enumeration, the resulting behavior is + /// undefined. Tests are included to cover two behavioral scenarios: + /// - Throwing an InvalidOperationException + /// - Execute MoveNext or Reset. + /// + /// If this property is set to true, the tests ensure that the exception is thrown. The default value is + /// . + /// + protected virtual bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException; + + /// Whether the enumerator returned from GetEnumerator is a singleton instance when the collection is empty. + protected virtual bool Enumerator_Empty_UsesSingletonInstance => false; + /// /// Specifies whether this IEnumerable follows some sort of ordering pattern. /// @@ -113,7 +134,7 @@ private void RepeatTest( IEnumerable enumerable = GenericIEnumerableFactory(32); T[] items = enumerable.ToArray(); IEnumerator enumerator = enumerable.GetEnumerator(); - for (var i = 0; i < iters; i++) + for (int i = 0; i < iters; i++) { testCode(enumerator, items, i); if (!ResetImplemented) @@ -148,7 +169,7 @@ private void VerifyModifiedEnumerator( else { object? current = enumerator.Current; - for (var i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { Assert.Equal(expectedCurrent, current); current = enumerator.Current; @@ -189,7 +210,7 @@ private void VerifyEnumerator( bool needToMatchAllExpectedItems = count - startIndex == expectedItems.Length; if (validateStart) { - for (var i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { if (Enumerator_Current_UndefinedOperation_Throws) { @@ -197,7 +218,7 @@ private void VerifyEnumerator( } else { - var cur = enumerator.Current; + _ = enumerator.Current; } } } @@ -216,8 +237,8 @@ private void VerifyEnumerator( iterations++) { object? currentItem = enumerator.Current; - var itemFound = false; - for (var i = 0; i < itemsVisited.Length; ++i) + bool itemFound = false; + for (int i = 0; i < itemsVisited.Length; ++i) { if (!itemsVisited[i] && Equals( @@ -235,7 +256,7 @@ private void VerifyEnumerator( } Assert.True(itemFound, "itemFound"); - for (var i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { object? tempItem = enumerator.Current; Assert.Equal(currentItem, tempItem); @@ -243,15 +264,15 @@ private void VerifyEnumerator( } if (needToMatchAllExpectedItems) { - for (var i = 0; i < itemsVisited.Length; i++) + for (int i = 0; i < itemsVisited.Length; i++) { Assert.True(itemsVisited[i]); } } else { - var visitedItemCount = 0; - for (var i = 0; i < itemsVisited.Length; i++) + int visitedItemCount = 0; + for (int i = 0; i < itemsVisited.Length; i++) { if (itemsVisited[i]) { @@ -269,7 +290,7 @@ private void VerifyEnumerator( { object? currentItem = enumerator.Current; Assert.Equal(expectedItems[iterations], currentItem); - for (var i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { object? tempItem = enumerator.Current; Assert.Equal(currentItem, tempItem); @@ -285,7 +306,7 @@ private void VerifyEnumerator( if (validateEnd) { - for (var i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { Assert.False(enumerator.MoveNext(), "enumerator.MoveNext() returned true past the expected end."); @@ -295,7 +316,7 @@ private void VerifyEnumerator( } else { - var cur = enumerator.Current; + _ = enumerator.Current; } } } @@ -305,6 +326,30 @@ private void VerifyEnumerator( #region GetEnumerator() + [Fact] + public void IEnumerable_NonGeneric_GetEnumerator_EmptyCollection_UsesSingleton() + { + IEnumerable enumerable = GenericIEnumerableFactory(0); + + IEnumerator enumerator1 = enumerable.GetEnumerator(); + try + { + IEnumerator enumerator2 = enumerable.GetEnumerator(); + try + { + Assert.Equal(Enumerator_Empty_UsesSingletonInstance, ReferenceEquals(enumerator1, enumerator2)); + } + finally + { + if (enumerator2 is IDisposable d2) d2.Dispose(); + } + } + finally + { + if (enumerator1 is IDisposable d1) d1.Dispose(); + } + } + [Theory] [MemberData(nameof(ValidCollectionSizes))] public void IEnumerable_Generic_GetEnumerator_NoExceptionsWhileGetting(int count) @@ -384,7 +429,7 @@ public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedBeforeEnumeration_Th { if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.MoveNext()); } @@ -430,7 +475,7 @@ public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedDuringEnumeration_Th enumerator.MoveNext(); if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.MoveNext()); } @@ -474,7 +519,7 @@ public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedAfterEnumeration_Thr while (enumerator.MoveNext()) ; if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.MoveNext()); } @@ -512,7 +557,7 @@ public void IEnumerable_Generic_Enumerator_MoveNextHitsAllItems() RepeatTest( (enumerator, items) => { - var iterations = 0; + int iterations = 0; while (enumerator.MoveNext()) { iterations++; @@ -605,9 +650,7 @@ public void IEnumerable_Generic_Enumerator_Current_BeforeFirstMoveNext_Undefined IEnumerable enumerable = GenericIEnumerableFactory(count); using (IEnumerator enumerator = enumerable.GetEnumerator()) { - if (Enumerator_Current_UndefinedOperation_Throws) - Assert.Throws(() => enumerator.Current); - else if (Enumerator_Current_UndefinedOperation_Throws_On_Empty && count == 0) + if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throws : Enumerator_Current_UndefinedOperation_Throws) Assert.Throws(() => enumerator.Current); else current = enumerator.Current; @@ -623,9 +666,7 @@ public void IEnumerable_Generic_Enumerator_Current_AfterEndOfEnumerable_Undefine using (IEnumerator enumerator = enumerable.GetEnumerator()) { while (enumerator.MoveNext()) ; - if (Enumerator_Current_UndefinedOperation_Throws) - Assert.Throws(() => enumerator.Current); - else if (Enumerator_Current_UndefinedOperation_Throws_On_Empty && count == 0) + if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throws : Enumerator_Current_UndefinedOperation_Throws) Assert.Throws(() => enumerator.Current); else current = enumerator.Current; @@ -644,7 +685,7 @@ public void IEnumerable_Generic_Enumerator_Current_ModifiedDuringEnumeration_Und { if (ModifyEnumerable(enumerable)) { - if (Enumerator_Current_UndefinedOperation_Throws) + if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throws : Enumerator_Current_UndefinedOperation_Throws) Assert.Throws(() => enumerator.Current); else current = enumerator.Current; @@ -699,7 +740,7 @@ public void IEnumerable_Generic_Enumerator_Reset_ModifiedBeforeEnumeration_Throw { if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.Reset()); } @@ -742,7 +783,7 @@ public void IEnumerable_Generic_Enumerator_Reset_ModifiedDuringEnumeration_Throw enumerator.MoveNext(); if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.Reset()); } @@ -786,7 +827,7 @@ public void IEnumerable_Generic_Enumerator_Reset_ModifiedAfterEnumeration_Throws while (enumerator.MoveNext()) ; if (ModifyEnumerable(enumerable)) { - if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) { Assert.Throws(() => enumerator.Reset()); } @@ -841,7 +882,7 @@ public void IEnumerable_Generic_Enumerator_Reset() items.Length / 2, true, false); - for (var i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { Assert.Throws( () => enumerator.Reset()); @@ -857,7 +898,7 @@ public void IEnumerable_Generic_Enumerator_Reset() else if (iter == 2) { VerifyEnumerator(enumerator, items); - for (var i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { Assert.Throws( () => enumerator.Reset()); diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.NonGeneric.Tests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.NonGeneric.Tests.cs index 7117c5156ed86..1cba23d07e2cf 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.NonGeneric.Tests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/IEnumerable.NonGeneric.Tests.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -68,6 +68,31 @@ public abstract partial class IEnumerable_NonGeneric_Tests : TestBase /// protected virtual bool Enumerator_Current_UndefinedOperation_Throws => false; + /// + /// When calling MoveNext or Reset after modification of the enumeration, the resulting behavior is + /// undefined. Tests are included to cover two behavioral scenarios: + /// - Throwing an InvalidOperationException + /// - Execute MoveNext or Reset. + /// + /// If this property is set to true, the tests ensure that the exception is thrown. The default value is + /// true. + /// + protected virtual bool Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException => true; + + /// + /// When calling MoveNext or Reset after modification of an empty enumeration, the resulting behavior is + /// undefined. Tests are included to cover two behavioral scenarios: + /// - Throwing an InvalidOperationException + /// - Execute MoveNext or Reset. + /// + /// If this property is set to true, the tests ensure that the exception is thrown. The default value is + /// . + /// + protected virtual bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException; + + /// Whether the enumerator returned from GetEnumerator is a singleton instance when the collection is empty. + protected virtual bool Enumerator_Empty_UsesSingletonInstance => false; + /// /// Whether the collection can be serialized. /// @@ -91,6 +116,30 @@ protected enum EnumerableOrder #region GetEnumerator() + [Fact] + public void IEnumerable_NonGeneric_GetEnumerator_EmptyCollection_UsesSingleton() + { + IEnumerable enumerable = NonGenericIEnumerableFactory(0); + + IEnumerator enumerator1 = enumerable.GetEnumerator(); + try + { + IEnumerator enumerator2 = enumerable.GetEnumerator(); + try + { + Assert.Equal(Enumerator_Empty_UsesSingletonInstance, ReferenceEquals(enumerator1, enumerator2)); + } + finally + { + if (enumerator2 is IDisposable d2) d2.Dispose(); + } + } + finally + { + if (enumerator1 is IDisposable d1) d1.Dispose(); + } + } + [Theory] [MemberData(nameof(ValidCollectionSizes))] public void IEnumerable_NonGeneric_GetEnumerator_NoExceptionsWhileGetting(int count) @@ -148,7 +197,16 @@ public void IEnumerable_NonGeneric_Enumerator_MoveNext_ModifiedBeforeEnumeration IEnumerable enumerable = NonGenericIEnumerableFactory(count); IEnumerator enumerator = enumerable.GetEnumerator(); if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.MoveNext()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.MoveNext()); + } + else + { + _ = enumerator.MoveNext(); + } + } }); } @@ -160,10 +218,21 @@ public void IEnumerable_NonGeneric_Enumerator_MoveNext_ModifiedDuringEnumeration { IEnumerable enumerable = NonGenericIEnumerableFactory(count); IEnumerator enumerator = enumerable.GetEnumerator(); + for (int i = 0; i < count / 2; i++) enumerator.MoveNext(); + if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.MoveNext()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.MoveNext()); + } + else + { + enumerator.MoveNext(); + } + } }); } @@ -177,7 +246,16 @@ public void IEnumerable_NonGeneric_Enumerator_MoveNext_ModifiedAfterEnumeration_ IEnumerator enumerator = enumerable.GetEnumerator(); while (enumerator.MoveNext()) ; if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.MoveNext()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.MoveNext()); + } + else + { + _ = enumerator.MoveNext(); + } + } }); } @@ -297,7 +375,16 @@ public void IEnumerable_NonGeneric_Enumerator_Reset_ModifiedBeforeEnumeration_Th IEnumerable enumerable = NonGenericIEnumerableFactory(count); IEnumerator enumerator = enumerable.GetEnumerator(); if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.Reset()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.Reset()); + } + else + { + enumerator.Reset(); + } + } }); } @@ -309,10 +396,21 @@ public void IEnumerable_NonGeneric_Enumerator_Reset_ModifiedDuringEnumeration_Th { IEnumerable enumerable = NonGenericIEnumerableFactory(count); IEnumerator enumerator = enumerable.GetEnumerator(); + for (int i = 0; i < count / 2; i++) enumerator.MoveNext(); + if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.Reset()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.Reset()); + } + else + { + enumerator.Reset(); + } + } }); } @@ -326,7 +424,16 @@ public void IEnumerable_NonGeneric_Enumerator_Reset_ModifiedAfterEnumeration_Thr IEnumerator enumerator = enumerable.GetEnumerator(); while (enumerator.MoveNext()) ; if (ModifyEnumerable(enumerable)) - Assert.Throws(() => enumerator.Reset()); + { + if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.Reset()); + } + else + { + enumerator.Reset(); + } + } }); } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/IList.Generic.Tests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/IList.Generic.Tests.cs index a439fbefb972b..398c1771caada 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/IList.Generic.Tests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/IList.Generic.Tests.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/Common/tests/System/Collections/IList.Generic.Tests.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/IList.Generic.Tests.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -47,7 +47,7 @@ protected virtual IList GenericIListFactory(int count) /// protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) { - foreach (var item in base.GetModifyEnumerables(operations)) + foreach (ModifyEnumerable item in base.GetModifyEnumerables(operations)) yield return item; if (!AddRemoveClear_ThrowsNotSupported && (operations & ModifyOperation.Insert) == ModifyOperation.Insert) @@ -623,7 +623,7 @@ public void IList_Generic_CurrentAtEnd_AfterAdd(int count) while (enumerator.MoveNext()) ; // Go to end of enumerator T? current = default(T); - if (Enumerator_Current_UndefinedOperation_Throws) + if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throws : Enumerator_Current_UndefinedOperation_Throws) { Assert.Throws(() => enumerator.Current); // enumerator.Current should fail } @@ -639,7 +639,7 @@ public void IList_Generic_CurrentAtEnd_AfterAdd(int count) { collection.Add(CreateT(seed++)); - if (Enumerator_Current_UndefinedOperation_Throws) + if (count == 0 ? Enumerator_Empty_Current_UndefinedOperation_Throws : Enumerator_Current_UndefinedOperation_Throws) { Assert.Throws(() => enumerator.Current); // enumerator.Current should fail } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/IList.NonGeneric.Tests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/IList.NonGeneric.Tests.cs index 21c95b0c81b24..b255277313d0d 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/IList.NonGeneric.Tests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/IList.NonGeneric.Tests.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/Common/tests/System/Collections/IList.NonGeneric.Tests.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/IList.NonGeneric.Tests.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -1098,7 +1098,7 @@ public void IList_NonGeneric_CurrentAtEnd_AfterAdd(int count) } else { - var current = enumerator.Current; // Enumerator.Current should not fail + _ = enumerator.Current; // Enumerator.Current should not fail } // Test after add @@ -1113,7 +1113,7 @@ public void IList_NonGeneric_CurrentAtEnd_AfterAdd(int count) } else { - var current = enumerator.Current; // Enumerator.Current should not fail + _ = enumerator.Current; // Enumerator.Current should not fail } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.AddRange.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.AddRange.cs index ebf9518ed6e64..c11ec41353926 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.AddRange.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.AddRange.cs @@ -3,15 +3,18 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.AddRange.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.AddRange.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.CSharp; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests.Collections @@ -45,6 +48,66 @@ public void AddRange(EnumerableType enumerableType, int listLength, int enumerab }); } +#if true + [Fact] + public void NoAddRangeExtension() + { + foreach (var type in typeof(SegmentedList<>).Assembly.DefinedTypes) + { + foreach (var method in type.DeclaredMethods) + { + if (!method.IsStatic) + continue; + + if (method.Name is not "AddRange") + continue; + + if (method.GetParameters() is not [var firstParameter, var spanParameter]) + continue; + + // AddRange(SegmentedList, ReadOnlySpan) + if (firstParameter.ParameterType.GetGenericTypeDefinition() != typeof(SegmentedList<>)) + continue; + + if (spanParameter.ParameterType.GetGenericTypeDefinition() != typeof(ReadOnlySpan<>)) + continue; + + // If this fails, it means the extension necessary for the following tests has now been implemented + throw ExceptionUtilities.UnexpectedValue(method); + } + } + } +#else + [Theory] + [MemberData(nameof(ListTestData))] + public void AddRange_Span(EnumerableType enumerableType, int listLength, int enumerableLength, int numberOfMatchingElements, int numberOfDuplicateElements) + { + SegmentedList list = GenericListFactory(listLength); + SegmentedList listBeforeAdd = list.ToSegmentedList(); + Span span = CreateEnumerable(enumerableType, list, enumerableLength, numberOfMatchingElements, numberOfDuplicateElements).ToArray(); + list.AddRange(span); + + // Check that the first section of the List is unchanged + Assert.All(Enumerable.Range(0, listLength), index => + { + Assert.Equal(listBeforeAdd[index], list[index]); + }); + + // Check that the added elements are correct + for (int i = 0; i < enumerableLength; i++) + { + Assert.Equal(span[i], list[i + listLength]); + }; + } + + [Fact] + public void AddRange_NullList_ThrowsArgumentNullException() + { + Assert.Throws("list", () => SegmentedCollectionExtensions.AddRange(null!, default)); + Assert.Throws("list", () => SegmentedCollectionExtensions.AddRange(null!, new int[1])); + } +#endif + [Theory] [MemberData(nameof(ValidCollectionSizes))] public void AddRange_NullEnumerable_ThrowsArgumentNullException(int count) @@ -78,5 +141,28 @@ public void AddRange_AddSelfAsEnumerable_ThrowsExceptionWhenNotEmpty() Assert.Throws(() => list.AddRange(list.Where(_ => true))); Assert.Equal(6, list.Count); } + + [Fact] + public void AddRange_CollectionWithLargeCount_ThrowsOverflowException() + { + SegmentedList list = GenericListFactory(count: 1); + ICollection collection = new CollectionWithLargeCount(); + + Assert.Throws(() => list.AddRange(collection)); + } + + private class CollectionWithLargeCount : ICollection + { + public int Count => int.MaxValue; + + public bool IsReadOnly => throw new NotImplementedException(); + public void Add(T item) => throw new NotImplementedException(); + public void Clear() => throw new NotImplementedException(); + public bool Contains(T item) => throw new NotImplementedException(); + public void CopyTo(T[] array, int arrayIndex) => throw new NotImplementedException(); + public IEnumerator GetEnumerator() => throw new NotImplementedException(); + public bool Remove(T item) => throw new NotImplementedException(); + IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); + } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.AsNonGenericIList.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.AsNonGenericIList.cs index 59a62bebff477..3b5d91c54ab7a 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.AsNonGenericIList.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.AsNonGenericIList.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.AsNonGenericIList.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.AsNonGenericIList.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.BinarySearch.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.BinarySearch.cs index dd77b8937c8b8..596acbd9047fd 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.BinarySearch.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.BinarySearch.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.BinarySearch.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.BinarySearch.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Constructor.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Constructor.cs index 9ffe991fe5ab8..4da5ffbc048c9 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Constructor.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Constructor.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Constructor.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Constructor.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.ConvertAll.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.ConvertAll.cs index a145e496a69e9..869f61913ba23 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.ConvertAll.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.ConvertAll.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.ConvertAll.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.ConvertAll.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Find.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Find.cs index d89c4c1cfc600..137c4642564bb 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Find.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Find.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Find.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Find.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -293,6 +293,27 @@ public void Find_VerifyDuplicates(int count) } } + [Fact] + public void Find_ListSizeCanBeChanged() + { + SegmentedList expectedList = new SegmentedList() { 1, 2, 3, 2, 3, 4, 3, 4, 4 }; + + SegmentedList list = new SegmentedList() { 1, 2, 3 }; + + int result = list.Find(i => + { + if (i < 4) + { + list.Add(i + 1); + } + + return false; + }); + + Assert.Equal(0, result); + Assert.Equal(expectedList, list); + } + #endregion #region FindLast @@ -986,6 +1007,26 @@ public void FindAll_VerifyDuplicates(int count) VerifyList(list.FindAll(_alwaysFalseDelegate), new SegmentedList()); } + [Fact] + public void FindAll_ListSizeCanBeChanged() + { + SegmentedList list = new SegmentedList() { 1, 2, 3 }; + SegmentedList expectedList = new SegmentedList() { 1, 2, 3, 2, 3, 4, 3, 4, 4 }; + + SegmentedList result = list.FindAll(i => + { + if (i < 4) + { + list.Add(i + 1); + } + + return true; + }); + + Assert.Equal(expectedList, result); + Assert.Equal(expectedList, list); + } + #endregion } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.ForEach.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.ForEach.cs index 20dc1edc47c40..855e984200d23 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.ForEach.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.ForEach.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.ForEach.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.ForEach.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.IndexOf.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.IndexOf.cs index a3837d8d2379f..2813e2dc21c72 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.IndexOf.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.IndexOf.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.IndexOf.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.IndexOf.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Misc.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Misc.cs index e8a0ff4154548..6925e9d1336b9 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Misc.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Misc.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Misc.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Misc.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -210,10 +210,10 @@ public void InsertRangeValidations(T?[] items, Func> const #region GetRange - public void BasicGetRange(T[] items, int index, int count) + public void BasicGetRange(T[] items, int index, int count, bool useSlice) { SegmentedList list = new SegmentedList(items); - SegmentedList range = list.GetRange(index, count); + SegmentedList range = useSlice ? list.Slice(index, count) : list.GetRange(index, count); //ensure range is good for (int i = 0; i < count; i++) @@ -228,26 +228,44 @@ public void BasicGetRange(T[] items, int index, int count) } } - public void EnsureRangeIsReference(T[] items, T item, int index, int count) + public void BasicSliceSyntax(T[] items, int index, int count) { SegmentedList list = new SegmentedList(items); - SegmentedList range = list.GetRange(index, count); + SegmentedList range = list[index..(index + count)]; + + //ensure range is good + for (int i = 0; i < count; i++) + { + Assert.Equal(range[i], items[i + index]); //String.Format("Err_170178aqhbpa Expected item: {0} at: {1} actual: {2}", items[i + index], i, range[i]) + } + + //ensure no side effects + for (int i = 0; i < items.Length; i++) + { + Assert.Equal(list[i], items[i]); //String.Format("Err_00125698ahpap Expected item: {0} at: {1} actual: {2}", items[i], i, list[i]) + } + } + + public void EnsureRangeIsReference(T[] items, T item, int index, int count, bool useSlice) + { + SegmentedList list = new SegmentedList(items); + SegmentedList range = useSlice ? list[index..(index + count)] : list.GetRange(index, count); T tempItem = list[index]; range[0] = item; Assert.Equal(list[index], tempItem); //String.Format("Err_707811hapba Expected item: {0} at: {1} actual: {2}", tempItem, index, list[index]) } - public void EnsureThrowsAfterModification(T[] items, T item, int index, int count) + public void EnsureThrowsAfterModification(T[] items, T item, int index, int count, bool useSlice) { SegmentedList list = new SegmentedList(items); - SegmentedList range = list.GetRange(index, count); + SegmentedList range = useSlice ? list[index..(index + count)] : list.GetRange(index, count); T tempItem = list[index]; list[index] = item; Assert.Equal(range[0], tempItem); //String.Format("Err_1221589ajpa Expected item: {0} at: {1} actual: {2}", tempItem, 0, range[0]) } - public void GetRangeValidations(T[] items) + public void GetRangeValidations(T[] items, bool useSlice) { // //Always send items.Length is even @@ -284,7 +302,12 @@ public void GetRangeValidations(T[] items) for (int i = 0; i < bad.Length; i++) { - Assert.Throws(null, () => list.GetRange(bad[i], bad[++i])); //"ArgumentException expected." + Assert.Throws(null, () => + { + int index = bad[i]; + int count = bad[++i]; + return useSlice ? list.Slice(index, count) : list.GetRange(index, count); + }); //"ArgumentException expected." } bad = new int[] { @@ -306,7 +329,12 @@ public void GetRangeValidations(T[] items) for (int i = 0; i < bad.Length; i++) { - Assert.Throws(() => list.GetRange(bad[i], bad[++i])); //"ArgumentOutOfRangeException expected." + Assert.Throws(() => + { + int index = bad[i]; + int count = bad[++i]; + return useSlice ? list.Slice(index, count) : list.GetRange(index, count); + }); //"ArgumentOutOfRangeException expected." } } @@ -839,46 +867,69 @@ public static void InsertRangeTests_Negative() StringDriver.InsertRangeValidations(stringArr1, StringDriver.ConstructTestEnumerable); } - [Fact] - public static void GetRangeTests() + [Theory] + [InlineData(true)] + [InlineData(false)] + public static void GetRangeTests(bool useSlice) { Driver IntDriver = new Driver(); int[] intArr1 = new int[100]; for (int i = 0; i < 100; i++) intArr1[i] = i; - IntDriver.BasicGetRange(intArr1, 50, 50); - IntDriver.BasicGetRange(intArr1, 0, 50); - IntDriver.BasicGetRange(intArr1, 50, 25); - IntDriver.BasicGetRange(intArr1, 0, 25); - IntDriver.BasicGetRange(intArr1, 75, 25); - IntDriver.BasicGetRange(intArr1, 0, 100); - IntDriver.BasicGetRange(intArr1, 0, 99); - IntDriver.BasicGetRange(intArr1, 1, 1); - IntDriver.BasicGetRange(intArr1, 99, 1); - IntDriver.EnsureRangeIsReference(intArr1, 101, 0, 10); - IntDriver.EnsureThrowsAfterModification(intArr1, 10, 10, 10); + IntDriver.BasicGetRange(intArr1, 50, 50, useSlice); + IntDriver.BasicGetRange(intArr1, 0, 50, useSlice); + IntDriver.BasicGetRange(intArr1, 50, 25, useSlice); + IntDriver.BasicGetRange(intArr1, 0, 25, useSlice); + IntDriver.BasicGetRange(intArr1, 75, 25, useSlice); + IntDriver.BasicGetRange(intArr1, 0, 100, useSlice); + IntDriver.BasicGetRange(intArr1, 0, 99, useSlice); + IntDriver.BasicGetRange(intArr1, 1, 1, useSlice); + IntDriver.BasicGetRange(intArr1, 99, 1, useSlice); + IntDriver.EnsureRangeIsReference(intArr1, 101, 0, 10, useSlice); + IntDriver.EnsureThrowsAfterModification(intArr1, 10, 10, 10, useSlice); Driver StringDriver = new Driver(); string[] stringArr1 = new string[100]; for (int i = 0; i < 100; i++) stringArr1[i] = "SomeTestString" + i.ToString(); - StringDriver.BasicGetRange(stringArr1, 50, 50); - StringDriver.BasicGetRange(stringArr1, 0, 50); - StringDriver.BasicGetRange(stringArr1, 50, 25); - StringDriver.BasicGetRange(stringArr1, 0, 25); - StringDriver.BasicGetRange(stringArr1, 75, 25); - StringDriver.BasicGetRange(stringArr1, 0, 100); - StringDriver.BasicGetRange(stringArr1, 0, 99); - StringDriver.BasicGetRange(stringArr1, 1, 1); - StringDriver.BasicGetRange(stringArr1, 99, 1); - StringDriver.EnsureRangeIsReference(stringArr1, "SometestString101", 0, 10); - StringDriver.EnsureThrowsAfterModification(stringArr1, "str", 10, 10); + StringDriver.BasicGetRange(stringArr1, 50, 50, useSlice); + StringDriver.BasicGetRange(stringArr1, 0, 50, useSlice); + StringDriver.BasicGetRange(stringArr1, 50, 25, useSlice); + StringDriver.BasicGetRange(stringArr1, 0, 25, useSlice); + StringDriver.BasicGetRange(stringArr1, 75, 25, useSlice); + StringDriver.BasicGetRange(stringArr1, 0, 100, useSlice); + StringDriver.BasicGetRange(stringArr1, 0, 99, useSlice); + StringDriver.BasicGetRange(stringArr1, 1, 1, useSlice); + StringDriver.BasicGetRange(stringArr1, 99, 1, useSlice); + StringDriver.EnsureRangeIsReference(stringArr1, "SometestString101", 0, 10, useSlice); + StringDriver.EnsureThrowsAfterModification(stringArr1, "str", 10, 10, useSlice); } [Fact] - public static void GetRangeTests_Negative() + public static void SlicingWorks() + { + Driver IntDriver = new Driver(); + int[] intArr1 = new int[100]; + for (int i = 0; i < 100; i++) + intArr1[i] = i; + + IntDriver.BasicSliceSyntax(intArr1, 50, 50); + IntDriver.BasicSliceSyntax(intArr1, 0, 50); + IntDriver.BasicSliceSyntax(intArr1, 50, 25); + IntDriver.BasicSliceSyntax(intArr1, 0, 25); + IntDriver.BasicSliceSyntax(intArr1, 75, 25); + IntDriver.BasicSliceSyntax(intArr1, 0, 100); + IntDriver.BasicSliceSyntax(intArr1, 0, 99); + IntDriver.BasicSliceSyntax(intArr1, 1, 1); + IntDriver.BasicSliceSyntax(intArr1, 99, 1); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public static void GetRangeTests_Negative(bool useSlice) { Driver IntDriver = new Driver(); int[] intArr1 = new int[100]; @@ -890,8 +941,8 @@ public static void GetRangeTests_Negative() for (int i = 0; i < 100; i++) stringArr1[i] = "SomeTestString" + i.ToString(); - StringDriver.GetRangeValidations(stringArr1); - IntDriver.GetRangeValidations(intArr1); + StringDriver.GetRangeValidations(stringArr1, useSlice); + IntDriver.GetRangeValidations(intArr1, useSlice); } [Fact] @@ -1105,5 +1156,25 @@ public static void TrueForAllTests_Negative() intDriver.TrueForAll_VerifyExceptions(intArray); stringDriver.TrueForAll_VerifyExceptions(stringArray); } + + [Fact] + public static void TrueForAll_ListSizeCanBeChanged() + { + SegmentedList list = new SegmentedList() { 1, 2, 3 }; + SegmentedList expectedList = new SegmentedList { 1, 2, 3, 2, 3, 4, 3, 4, 4 }; + + bool result = list.TrueForAll(i => + { + if (i < 4) + { + list.Add(i + 1); + } + + return true; + }); + + Assert.True(result); + Assert.Equal(expectedList, list); + } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Remove.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Remove.cs index 0acb0f1056803..2413ae6e9dcbe 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Remove.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Remove.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Remove.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Remove.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Reverse.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Reverse.cs index 7dc16dedbac33..6dfe72ab8ce10 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Reverse.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Reverse.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Reverse.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Reverse.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Sort.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Sort.cs index 65c398e43ebce..b250c18de1e2a 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Sort.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.Sort.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Sort.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.Sort.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.cs index 41ae501060893..1a05024b7c6ae 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.Tests.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -22,6 +22,9 @@ public abstract partial class SegmentedList_Generic_Tests : IList_Generic_Tes where T : notnull { #region IList Helper Methods + protected override bool Enumerator_Empty_UsesSingletonInstance => true; + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => false; + protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false; protected override IList GenericIListFactory() { diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.cs index 4351de1a97535..60f400c7b5e94 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/SegmentedList.Generic.cs @@ -3,14 +3,13 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections/tests/Generic/List/List.Generic.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Collections/tests/Generic/List/List.Generic.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. using System; using System.Collections.Generic; -using System.Runtime.InteropServices; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Test.Utilities; @@ -50,8 +49,6 @@ protected override string CreateT(int seed) protected override bool IsReadOnly => true; - protected override bool Enumerator_Current_UndefinedOperation_Throws_On_Empty => RuntimeUtilities.IsCoreClr8OrHigherRuntime; - protected override IList GenericIListFactory(int setLength) { return GenericListFactory(setLength).AsReadOnly(); @@ -63,6 +60,8 @@ protected override IList GenericIListFactory() } protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new SegmentedList(); + + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => RuntimeUtilities.IsCoreClr8OrHigherRuntime; } public class SegmentedList_Generic_Tests_int_ReadOnly : SegmentedList_Generic_Tests @@ -75,8 +74,6 @@ protected override int CreateT(int seed) protected override bool IsReadOnly => true; - protected override bool Enumerator_Current_UndefinedOperation_Throws_On_Empty => RuntimeUtilities.IsCoreClr8OrHigherRuntime; - protected override IList GenericIListFactory(int setLength) { return GenericListFactory(setLength).AsReadOnly(); @@ -88,5 +85,7 @@ protected override IList GenericIListFactory() } protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) => new SegmentedList(); + + protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => RuntimeUtilities.IsCoreClr8OrHigherRuntime; } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/TestBase.Generic.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/TestBase.Generic.cs index 9dd55bb59224e..df34da7685f64 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/TestBase.Generic.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/TestBase.Generic.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/Common/tests/System/Collections/TestBase.Generic.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/TestBase.Generic.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -47,42 +47,49 @@ public abstract class TestBase : TestBase /// MemberData to be passed to tests that take an IEnumerable{T}. This method returns every permutation of /// EnumerableType to test on (e.g. HashSet, Queue), and size of set to test with (e.g. 0, 1, etc.). /// - public static IEnumerable EnumerableTestData() + public static IEnumerable EnumerableTestData() => + ((IEnumerable)Enum.GetValues(typeof(EnumerableType))).SelectMany(GetEnumerableTestData); + + /// + /// MemberData to be passed to tests that take an IEnumerable{T}. This method returns results for various + /// sizes of sets to test with (e.g. 0, 1, etc.) but only for List. + /// + public static IEnumerable ListTestData() => + GetEnumerableTestData(EnumerableType.List); + + protected static IEnumerable GetEnumerableTestData(EnumerableType enumerableType) { foreach (object[] collectionSizeArray in ValidCollectionSizes()) { - foreach (EnumerableType enumerableType in Enum.GetValues(typeof(EnumerableType))) + int count = (int)collectionSizeArray[0]; + yield return new object[] { enumerableType, count, 0, 0, 0 }; // Empty Enumerable + yield return new object[] { enumerableType, count, count + 1, 0, 0 }; // Enumerable that is 1 larger + + if (count >= 1) + { + yield return new object[] { enumerableType, count, count, 0, 0 }; // Enumerable of the same size + yield return new object[] { enumerableType, count, count - 1, 0, 0 }; // Enumerable that is 1 smaller + yield return new object[] { enumerableType, count, count, 1, 0 }; // Enumerable of the same size with 1 matching element + yield return new object[] { enumerableType, count, count + 1, 1, 0 }; // Enumerable that is 1 longer with 1 matching element + yield return new object[] { enumerableType, count, count, count, 0 }; // Enumerable with all elements matching + yield return new object[] { enumerableType, count, count + 1, count, 0 }; // Enumerable with all elements matching plus one extra + } + + if (count >= 2) + { + yield return new object[] { enumerableType, count, count - 1, 1, 0 }; // Enumerable that is 1 smaller with 1 matching element + yield return new object[] { enumerableType, count, count + 2, 2, 0 }; // Enumerable that is 2 longer with 2 matching element + yield return new object[] { enumerableType, count, count - 1, count - 1, 0 }; // Enumerable with all elements matching minus one + yield return new object[] { enumerableType, count, count, 2, 0 }; // Enumerable of the same size with 2 matching element + if ((enumerableType == EnumerableType.List || enumerableType == EnumerableType.Queue)) + yield return new object[] { enumerableType, count, count, 0, 1 }; // Enumerable with 1 element duplicated + } + + if (count >= 3) { - int count = (int)collectionSizeArray[0]; - yield return new object[] { enumerableType, count, 0, 0, 0 }; // Empty Enumerable - yield return new object[] { enumerableType, count, count + 1, 0, 0 }; // Enumerable that is 1 larger - - if (count >= 1) - { - yield return new object[] { enumerableType, count, count, 0, 0 }; // Enumerable of the same size - yield return new object[] { enumerableType, count, count - 1, 0, 0 }; // Enumerable that is 1 smaller - yield return new object[] { enumerableType, count, count, 1, 0 }; // Enumerable of the same size with 1 matching element - yield return new object[] { enumerableType, count, count + 1, 1, 0 }; // Enumerable that is 1 longer with 1 matching element - yield return new object[] { enumerableType, count, count, count, 0 }; // Enumerable with all elements matching - yield return new object[] { enumerableType, count, count + 1, count, 0 }; // Enumerable with all elements matching plus one extra - } - - if (count >= 2) - { - yield return new object[] { enumerableType, count, count - 1, 1, 0 }; // Enumerable that is 1 smaller with 1 matching element - yield return new object[] { enumerableType, count, count + 2, 2, 0 }; // Enumerable that is 2 longer with 2 matching element - yield return new object[] { enumerableType, count, count - 1, count - 1, 0 }; // Enumerable with all elements matching minus one - yield return new object[] { enumerableType, count, count, 2, 0 }; // Enumerable of the same size with 2 matching element - if ((enumerableType == EnumerableType.List || enumerableType == EnumerableType.Queue)) - yield return new object[] { enumerableType, count, count, 0, 1 }; // Enumerable with 1 element duplicated - } - - if (count >= 3) - { - if ((enumerableType == EnumerableType.List || enumerableType == EnumerableType.Queue)) - yield return new object[] { enumerableType, count, count, 0, 1 }; // Enumerable with all elements duplicated - yield return new object[] { enumerableType, count, count - 1, 2, 0 }; // Enumerable that is 1 smaller with 2 matching elements - } + if ((enumerableType == EnumerableType.List || enumerableType == EnumerableType.Queue)) + yield return new object[] { enumerableType, count, count, 0, 1 }; // Enumerable with all elements duplicated + yield return new object[] { enumerableType, count, count - 1, 2, 0 }; // Enumerable that is 1 smaller with 2 matching elements } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/List/TestBase.NonGeneric.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/List/TestBase.NonGeneric.cs index eb71b35efed54..606ce30dd6678 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/List/TestBase.NonGeneric.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/List/TestBase.NonGeneric.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/Common/tests/System/Collections/TestBase.NonGeneric.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/TestBase.NonGeneric.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs b/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs index c660ffa62b568..fc559b641a539 100644 --- a/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.IO; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -291,6 +292,26 @@ public void IsSameDirectoryOrChildOfNegativeTests() Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\A\B\C", @"C:\A\B\C\D")); } + [ConditionalFact(typeof(WindowsOnly))] + public void IsSameDirectoryOrChildOfSpecifyingCaseSensitivity_Windows() + { + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\a\B\C", @"C:\A\B", StringComparison.OrdinalIgnoreCase)); + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\A\b\C", @"C:\A\B", StringComparison.OrdinalIgnoreCase)); + + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\a\B\C", @"C:\A\B", StringComparison.Ordinal)); + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\A\b\C", @"C:\A\B", StringComparison.Ordinal)); + } + + [ConditionalFact(typeof(UnixLikeOnly))] + public void IsSameDirectoryOrChildOfSpecifyingCaseSensitivity_Unix() + { + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"/a/B/C", @"/A/B", StringComparison.OrdinalIgnoreCase)); + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"/A/b/C", @"/A/B", StringComparison.OrdinalIgnoreCase)); + + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"/a/B/C", @"/A/B", StringComparison.Ordinal)); + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"/A/b/C", @"/A/B", StringComparison.Ordinal)); + } + [Fact] public void IsValidFilePath() { @@ -399,5 +420,20 @@ public void GetRelativePath_EnsureNo_IndexOutOfRangeException_Unix() var result = PathUtilities.GetRelativePath(@"/A/B/", @"/A/B"); Assert.Equal(expected, result); } + + [Theory] + [InlineData(@"//a/b/c", @"//a/b/c")] + [InlineData(@"/a\b/c/", @"/a/b/c/")] + [InlineData(@"\a\b/c/", @"/a/b/c/")] + [InlineData(@"C:\\a", @"C:/a")] + [InlineData(@"C:\a\b\c\", @"C:/a/b/c/")] + [InlineData(@"/\a", @"//a")] + [InlineData(@"a\\\b", @"a/b")] + [InlineData(@"\\\a\b\c", @"///a/b/c")] + [InlineData(@"\\\\a\b\c", @"///a/b/c")] + public void CollapseWithForwardSlash(string input, string output) + { + AssertEx.Equal(output, PathUtilities.CollapseWithForwardSlash(input.AsSpan())); + } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Symbols/SpecialTypeTests.cs b/src/Compilers/Core/CodeAnalysisTest/Symbols/SpecialTypeTests.cs new file mode 100644 index 0000000000000..4689c2b19fc62 --- /dev/null +++ b/src/Compilers/Core/CodeAnalysisTest/Symbols/SpecialTypeTests.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests +{ + public class SpecialTypeTests + { + [Fact] + public void ExtendedSpecialType_ToString() + { + AssertEx.Equal("0", ((ExtendedSpecialType)SpecialType.None).ToString()); + AssertEx.Equal("System_Object", ((ExtendedSpecialType)1).ToString()); + AssertEx.Equal("System_Runtime_CompilerServices_InlineArrayAttribute", ((ExtendedSpecialType)SpecialType.Count).ToString()); + AssertEx.Equal("System_ReadOnlySpan_T", ((ExtendedSpecialType)InternalSpecialType.First).ToString()); + AssertEx.Equal("System_ReadOnlySpan_T", ((ExtendedSpecialType)InternalSpecialType.System_ReadOnlySpan_T).ToString()); + AssertEx.Equal("System_Reflection_MethodInfo", ((ExtendedSpecialType)(InternalSpecialType.NextAvailable - 1)).ToString()); + AssertEx.Equal("52", ((ExtendedSpecialType)InternalSpecialType.NextAvailable).ToString()); + } + } +} diff --git a/src/Compilers/Core/CommandLine/BuildProtocol.cs b/src/Compilers/Core/CommandLine/BuildProtocol.cs index b2fa1ec0b622a..e315beaf65a28 100644 --- a/src/Compilers/Core/CommandLine/BuildProtocol.cs +++ b/src/Compilers/Core/CommandLine/BuildProtocol.cs @@ -132,16 +132,8 @@ public static async Task ReadAsync(Stream inStream, CancellationTo throw new ArgumentException($"Request is over {MaximumRequestSize >> 20}MB in length"); } - cancellationToken.ThrowIfCancellationRequested(); - - // Read the full request - var requestBuffer = new byte[length]; - await ReadAllAsync(inStream, requestBuffer, length, cancellationToken).ConfigureAwait(false); - - 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(inStream, Encoding.Unicode, leaveOpen: true); var requestId = reader.ReadString(); var language = (RequestLanguage)reader.ReadUInt32(); var compilerHash = reader.ReadString(); diff --git a/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs b/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs index b2a9dfe48325f..68ceaeecff859 100644 --- a/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs +++ b/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs @@ -753,7 +753,7 @@ private bool ValidateBootstrapResponse(BuildResponse? response) Log.LogError($"Critical error {responseType} when building"); return false; case BuildResponse.ResponseType.Rejected: - Log.LogError($"Compiler request rejected"); + Log.LogError($"Compiler request rejected: {((RejectedBuildResponse)response!).Reason}"); return false; case BuildResponse.ResponseType.CannotConnect: if (Interlocked.Increment(ref s_connectFailedCount) > maxCannotConnectCount) diff --git a/src/Compilers/Core/Portable/CodeGen/ItemTokenMap.cs b/src/Compilers/Core/Portable/CodeGen/ItemTokenMap.cs index f0cdc1483e780..7d387f9def7ea 100644 --- a/src/Compilers/Core/Portable/CodeGen/ItemTokenMap.cs +++ b/src/Compilers/Core/Portable/CodeGen/ItemTokenMap.cs @@ -62,7 +62,7 @@ public T GetItem(uint token) } } - public IEnumerable GetAllItems() + public T[] CopyItems() { lock (_items) { diff --git a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs index f837871a5f7dc..51ccab0fd9398 100644 --- a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs +++ b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs @@ -636,6 +636,32 @@ public static async Task AnyAsync(this ImmutableArray array, F return default; } + public static TValue? Single(this ImmutableArray array, Func predicate, TArg arg) + { + var hasValue = false; + TValue? value = default; + foreach (var item in array) + { + if (predicate(item, arg)) + { + if (hasValue) + { + throw ExceptionUtilities.Unreachable(); + } + + value = item; + hasValue = true; + } + } + + if (!hasValue) + { + throw ExceptionUtilities.Unreachable(); + } + + return value; + } + /// /// Casts the immutable array of a Type to an immutable array of its base type. /// diff --git a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs index eaa8de216fe95..a597f86422c39 100644 --- a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs +++ b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs @@ -183,7 +183,7 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath) var sectionKey = _sectionKeyPool.Allocate(); - var normalizedPath = PathUtilities.NormalizeWithForwardSlash(sourcePath); + var normalizedPath = PathUtilities.CollapseWithForwardSlash(sourcePath.AsSpan()); normalizedPath = PathUtilities.ExpandAbsolutePathWithRelativeParts(normalizedPath); // If we have a global config, add any sections that match the full path. We can have at most one section since @@ -204,7 +204,7 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath) { var config = _analyzerConfigs[analyzerConfigIndex]; - if (normalizedPath.StartsWith(config.NormalizedDirectory, StringComparison.Ordinal)) + if (PathUtilities.IsSameDirectoryOrChildOf(normalizedPath, config.NormalizedDirectory, StringComparison.Ordinal)) { // If this config is a root config, then clear earlier options since they don't apply // to this source file. diff --git a/src/Compilers/Core/Portable/Compilation/SemanticModel.cs b/src/Compilers/Core/Portable/Compilation/SemanticModel.cs index 2df6f1d68b4c6..85f861b6a26bc 100644 --- a/src/Compilers/Core/Portable/Compilation/SemanticModel.cs +++ b/src/Compilers/Core/Portable/Compilation/SemanticModel.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis { /// /// Allows asking semantic questions about a tree of syntax nodes in a Compilation. Typically, - /// an instance is obtained by a call to GetBinding on a Compilation or Compilation. + /// an instance is obtained by a call to . /// /// /// An instance of SemanticModel caches local symbols and semantic information. Thus, it diff --git a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs index 1bc7079e2a2d8..cd10a5c8a8eca 100644 --- a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs +++ b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs @@ -368,11 +368,11 @@ public Cci.IAssemblyReference GetContainingAssembly(EmitContext context) } /// - /// Returns User Strings referenced from the IL in the module. + /// Returns copy of User Strings referenced from the IL in the module. /// - public IEnumerable GetStrings() + public string[] CopyStrings() { - return _stringsInILMap.GetAllItems(); + return _stringsInILMap.CopyItems(); } public uint GetFakeSymbolTokenForIL(Cci.IReference symbol, SyntaxNode syntaxNode, DiagnosticBag diagnostics) diff --git a/src/Compilers/Core/Portable/ExtendedSpecialType.cs b/src/Compilers/Core/Portable/ExtendedSpecialType.cs index 0010ff28aa3d9..a5b5eb8212982 100644 --- a/src/Compilers/Core/Portable/ExtendedSpecialType.cs +++ b/src/Compilers/Core/Portable/ExtendedSpecialType.cs @@ -58,6 +58,16 @@ public override int GetHashCode() public override string ToString() { + if (_value > (int)SpecialType.None && _value <= (int)SpecialType.Count) + { + return ((SpecialType)_value).ToString(); + } + + if (_value >= (int)InternalSpecialType.First && _value < (int)InternalSpecialType.NextAvailable) + { + return ((InternalSpecialType)_value).ToString(); + } + return _value.ToString(); } } diff --git a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs index 0617a16e08dfd..161f93819ddab 100644 --- a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs +++ b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; @@ -159,7 +160,7 @@ public static string RemoveExtension(string path) return null; } - internal static bool IsSameDirectoryOrChildOf(string child, string parent) + internal static bool IsSameDirectoryOrChildOf(string child, string parent, StringComparison comparison = StringComparison.OrdinalIgnoreCase) { parent = RemoveTrailingDirectorySeparator(parent); string? currentChild = child; @@ -167,7 +168,7 @@ internal static bool IsSameDirectoryOrChildOf(string child, string parent) { currentChild = RemoveTrailingDirectorySeparator(currentChild); - if (currentChild.Equals(parent, StringComparison.OrdinalIgnoreCase)) + if (currentChild.Equals(parent, comparison)) { return true; } @@ -780,6 +781,42 @@ public static bool IsValidFilePath([NotNullWhen(true)] string? fullPath) public static string NormalizeWithForwardSlash(string p) => DirectorySeparatorChar == '/' ? p : p.Replace(DirectorySeparatorChar, '/'); + /// + /// Replaces all sequences of '\' or '/' with a single '/' but preserves UNC prefix '//'. + /// + public static string CollapseWithForwardSlash(ReadOnlySpan path) + { + var sb = new StringBuilder(path.Length); + + int start = 0; + if (path.Length > 1 && IsAnyDirectorySeparator(path[0]) && IsAnyDirectorySeparator(path[1])) + { + // Preserve UNC paths. + sb.Append("//"); + start = 2; + } + + bool wasDirectorySeparator = false; + for (int i = start; i < path.Length; i++) + { + if (IsAnyDirectorySeparator(path[i])) + { + if (!wasDirectorySeparator) + { + sb.Append('/'); + } + wasDirectorySeparator = true; + } + else + { + sb.Append(path[i]); + wasDirectorySeparator = false; + } + } + + return sb.ToString(); + } + /// /// Takes an absolute path and attempts to expand any '..' or '.' into their equivalent representation. /// diff --git a/src/Compilers/Core/Portable/InternalSpecialType.cs b/src/Compilers/Core/Portable/InternalSpecialType.cs index c43af826d71e1..23030db82f4ae 100644 --- a/src/Compilers/Core/Portable/InternalSpecialType.cs +++ b/src/Compilers/Core/Portable/InternalSpecialType.cs @@ -16,8 +16,6 @@ internal enum InternalSpecialType : sbyte // Value 0 represents an unknown type Unknown = SpecialType.None, - First = SpecialType.Count + 1, - /// /// Indicates that the type is from the COR library. /// @@ -27,7 +25,8 @@ internal enum InternalSpecialType : sbyte /// The should be used for that purpose instead /// This entry mostly exists so that compiler can tell this type apart when resolving other members of the COR library /// - System_ReadOnlySpan_T = First, + System_ReadOnlySpan_T = SpecialType.Count + 1, + First = System_ReadOnlySpan_T, System_IFormatProvider, diff --git a/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs index b299b637fc11a..5eaacc8f204c0 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs @@ -19,6 +19,18 @@ namespace Roslyn.Utilities { internal static partial class EnumerableExtensions { + public static int Count(this IEnumerable source, Func predicate, TArg arg) + { + var count = 0; + foreach (var v in source) + { + if (predicate(v, arg)) + count++; + } + + return count; + } + public static IEnumerable Do(this IEnumerable source, Action action) { if (source == null) diff --git a/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs index 36ead9791da22..d9dcd6529bed3 100644 --- a/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs @@ -9,6 +9,7 @@ using System.Reflection.Metadata.Ecma335; using System.Threading; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Emit; using Roslyn.Utilities; using EmitContext = Microsoft.CodeAnalysis.Emit.EmitContext; @@ -28,7 +29,7 @@ internal sealed class FullMetadataWriter : MetadataWriter private readonly Dictionary _fieldDefIndex; private readonly Dictionary _methodDefIndex; - private readonly Dictionary _parameterListIndex; + private readonly SegmentedDictionary _parameterListIndex; private readonly HeapOrReferenceIndex _assemblyRefIndex; private readonly HeapOrReferenceIndex _moduleRefIndex; @@ -101,7 +102,7 @@ private FullMetadataWriter( _fieldDefIndex = new Dictionary(numTypeDefsGuess, ReferenceEqualityComparer.Instance); _methodDefIndex = new Dictionary(numTypeDefsGuess, ReferenceEqualityComparer.Instance); - _parameterListIndex = new Dictionary(numMethods, ReferenceEqualityComparer.Instance); + _parameterListIndex = new SegmentedDictionary(numMethods, ReferenceEqualityComparer.Instance); _assemblyRefIndex = new HeapOrReferenceIndex(this); _moduleRefIndex = new HeapOrReferenceIndex(this); @@ -430,13 +431,13 @@ private void CreateIndicesFor(IMethodDefinition methodDef) private readonly struct DefinitionIndex where T : class, IReference { // IReference to RowId - private readonly Dictionary _index; - private readonly List _rows; + private readonly SegmentedDictionary _index; + private readonly SegmentedList _rows; public DefinitionIndex(int capacity) { - _index = new Dictionary(capacity, ReferenceEqualityComparer.Instance); - _rows = new List(capacity); + _index = new SegmentedDictionary(capacity, ReferenceEqualityComparer.Instance); + _rows = new SegmentedList(capacity); } public bool TryGetValue(T item, out int rowId) diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs index efc9fa1db1573..e3791fc94ce01 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -424,7 +424,7 @@ private bool IsMinimalDelta private object[] _pseudoSymbolTokenToReferenceMap; private UserStringHandle[] _pseudoStringTokenToTokenMap; private bool _userStringTokenOverflow; - private List _pseudoStringTokenToStringMap; + private string[] _pseudoStringTokenToStringMap; private ReferenceIndexer _referenceVisitor; protected readonly MetadataBuilder metadata; @@ -489,8 +489,8 @@ private void CreateIndices() private void CreateUserStringIndices() { - _pseudoStringTokenToStringMap = [.. module.GetStrings()]; - _pseudoStringTokenToTokenMap = new UserStringHandle[_pseudoStringTokenToStringMap.Count]; + _pseudoStringTokenToStringMap = module.CopyStrings(); + _pseudoStringTokenToTokenMap = new UserStringHandle[_pseudoStringTokenToStringMap.Length]; } private void CreateIndicesForModule() diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider.ImmutableArrayValueComparer.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider.ImmutableArrayValueComparer.cs deleted file mode 100644 index d1cadedcf503d..0000000000000 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider.ImmutableArrayValueComparer.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis; - -public partial struct SyntaxValueProvider -{ - private class ImmutableArrayValueComparer : IEqualityComparer> - { - public static readonly IEqualityComparer> Instance = new ImmutableArrayValueComparer(); - - public bool Equals(ImmutableArray x, ImmutableArray y) - { - if (x == y) - return true; - - return x.SequenceEqual(y, 0, static (a, b, _) => EqualityComparer.Default.Equals(a, b)); - } - - public int GetHashCode(ImmutableArray obj) - { - var hashCode = 0; - foreach (var value in obj) - hashCode = Hash.Combine(hashCode, EqualityComparer.Default.GetHashCode(value!)); - - return hashCode; - } - } -} diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs index f2fa22112ded2..e51c2e58e7320 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -68,7 +68,7 @@ public partial struct SyntaxValueProvider // Create a provider that provides (and updates) the global aliases for any particular file when it is edited. var individualFileGlobalAliasesProvider = syntaxTreesProvider - .Where((info, _) => info.Info.HasFlag(SourceGeneratorSyntaxTreeInfo.ContainsGlobalAliases)) + .Where(static (info, _) => info.Info.HasFlag(SourceGeneratorSyntaxTreeInfo.ContainsGlobalAliases)) .Select((info, cancellationToken) => getGlobalAliasesInCompilationUnit(syntaxHelper, info.Tree.GetRoot(cancellationToken))) .WithTrackingName("individualFileGlobalAliases_ForAttribute"); @@ -76,7 +76,6 @@ public partial struct SyntaxValueProvider // file changes its global aliases or a file is added / removed from the compilation var collectedGlobalAliasesProvider = individualFileGlobalAliasesProvider .Collect() - .WithComparer(ImmutableArrayValueComparer.Instance) .WithTrackingName("collectedGlobalAliases_ForAttribute"); var allUpGlobalAliasesProvider = collectedGlobalAliasesProvider @@ -95,19 +94,19 @@ public partial struct SyntaxValueProvider allUpGlobalAliasesProvider = allUpGlobalAliasesProvider .Combine(compilationGlobalAliases) - .Select((tuple, _) => GlobalAliases.Concat(tuple.Left, tuple.Right)) + .Select(static (tuple, _) => GlobalAliases.Concat(tuple.Left, tuple.Right)) .WithTrackingName("allUpIncludingCompilationGlobalAliases_ForAttribute"); // Combine the two providers so that we reanalyze every file if the global aliases change, or we reanalyze a // particular file when it's compilation unit changes. var syntaxTreeAndGlobalAliasesProvider = syntaxTreesProvider - .Where((info, _) => info.Info.HasFlag(SourceGeneratorSyntaxTreeInfo.ContainsAttributeList)) + .Where(static (info, _) => info.Info.HasFlag(SourceGeneratorSyntaxTreeInfo.ContainsAttributeList)) .Combine(allUpGlobalAliasesProvider) .WithTrackingName("compilationUnitAndGlobalAliases_ForAttribute"); return syntaxTreeAndGlobalAliasesProvider .Select((tuple, c) => (tuple.Left.Tree, GetMatchingNodes(syntaxHelper, tuple.Right, tuple.Left.Tree, simpleName, predicate, c))) - .Where(tuple => tuple.Item2.Length > 0) + .Where(static tuple => tuple.Item2.Length > 0) .WithTrackingName("result_ForAttributeInternal"); static GlobalAliases getGlobalAliasesInCompilationUnit( diff --git a/src/Compilers/Core/Portable/SpecialMember.cs b/src/Compilers/Core/Portable/SpecialMember.cs index fd79b8c0988de..639018f55b7c9 100644 --- a/src/Compilers/Core/Portable/SpecialMember.cs +++ b/src/Compilers/Core/Portable/SpecialMember.cs @@ -191,6 +191,8 @@ internal enum SpecialMember System_Array__Empty, System_Array__SetValue, + System_Type__GetTypeFromHandle, + Count } } diff --git a/src/Compilers/Core/Portable/SpecialMembers.cs b/src/Compilers/Core/Portable/SpecialMembers.cs index 956e4c643c1dd..887f7aa8f6960 100644 --- a/src/Compilers/Core/Portable/SpecialMembers.cs +++ b/src/Compilers/Core/Portable/SpecialMembers.cs @@ -1262,7 +1262,7 @@ static SpecialMembers() // System_Reflection_MethodBase__GetMethodFromHandle (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)InternalSpecialType.System_Reflection_MethodBase, // DeclaringTypeId + (byte)InternalSpecialType.System_Reflection_MethodBase, // DeclaringTypeId 0, // Arity 1, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)InternalSpecialType.System_Reflection_MethodBase, // Return Type @@ -1270,7 +1270,7 @@ static SpecialMembers() // System_Reflection_MethodBase__GetMethodFromHandle2 (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)InternalSpecialType.System_Reflection_MethodBase, // DeclaringTypeId + (byte)InternalSpecialType.System_Reflection_MethodBase, // DeclaringTypeId 0, // Arity 2, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)InternalSpecialType.System_Reflection_MethodBase, // Return Type @@ -1279,26 +1279,34 @@ static SpecialMembers() // System_Array__get_Length (byte)MemberFlags.PropertyGet, // Flags - (byte)SpecialType.System_Array, // DeclaringTypeId + (byte)SpecialType.System_Array, // DeclaringTypeId 0, // Arity 0, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, // Return Type // System_Array__Empty (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)SpecialType.System_Array, // DeclaringTypeId + (byte)SpecialType.System_Array, // DeclaringTypeId 1, // Arity 0, // Method Signature (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.GenericMethodParameter, 0, // Return Type // System_Array__SetValue (byte)MemberFlags.Method, // Flags - (byte)SpecialType.System_Array, // DeclaringTypeId + (byte)SpecialType.System_Array, // DeclaringTypeId 0, // Arity 2, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, + + // System_Type__GetTypeFromHandle + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)InternalSpecialType.System_Type, // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)InternalSpecialType.System_Type, // Return Type + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_RuntimeTypeHandle, }; string[] allNames = new string[(int)SpecialMember.Count] @@ -1458,6 +1466,7 @@ static SpecialMembers() "get_Length", // System_Array__get_Length "Empty", // System_Array__Empty "SetValue", // System_Array__SetValue + "GetTypeFromHandle", // System_Type__GetTypeFromHandle }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs index dab79d5a56a83..cf440cccf3c85 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs @@ -181,6 +181,7 @@ static AttributeDescription() private static readonly byte[] s_signature_HasThis_Void_Type_String = new byte[] { (byte)SignatureAttributes.Instance, 2, Void, TypeHandle, (byte)TypeHandleTarget.SystemType, String }; private static readonly byte[] s_signature_HasThis_Void_String_Int32_Int32 = new byte[] { (byte)SignatureAttributes.Instance, 3, Void, String, Int32, Int32 }; + private static readonly byte[] s_signature_HasThis_Void_Int32_String = new byte[] { (byte)SignatureAttributes.Instance, 2, Void, Int32, String }; private static readonly byte[] s_signature_HasThis_Void_SzArray_Boolean = new byte[] { (byte)SignatureAttributes.Instance, 1, Void, SzArray, Boolean }; private static readonly byte[] s_signature_HasThis_Void_SzArray_Byte = new byte[] { (byte)SignatureAttributes.Instance, 1, Void, SzArray, Byte }; @@ -225,7 +226,7 @@ static AttributeDescription() private static readonly byte[][] s_signaturesOfMemberNotNullAttribute = { s_signature_HasThis_Void_String, s_signature_HasThis_Void_SzArray_String }; private static readonly byte[][] s_signaturesOfMemberNotNullWhenAttribute = { s_signature_HasThis_Void_Boolean_String, s_signature_HasThis_Void_Boolean_SzArray_String }; private static readonly byte[][] s_signaturesOfFixedBufferAttribute = { s_signature_HasThis_Void_Type_Int32 }; - private static readonly byte[][] s_signaturesOfInterceptsLocationAttribute = { s_signature_HasThis_Void_String_Int32_Int32 }; + private static readonly byte[][] s_signaturesOfInterceptsLocationAttribute = { s_signature_HasThis_Void_String_Int32_Int32, s_signature_HasThis_Void_Int32_String }; private static readonly byte[][] s_signaturesOfPrincipalPermissionAttribute = { s_signature_HasThis_Void_SecurityAction }; private static readonly byte[][] s_signaturesOfPermissionSetAttribute = { s_signature_HasThis_Void_SecurityAction }; diff --git a/src/Compilers/Core/Portable/Text/ChangedText.cs b/src/Compilers/Core/Portable/Text/ChangedText.cs index 0422c82896e6f..c8f7d46b8d71d 100644 --- a/src/Compilers/Core/Portable/Text/ChangedText.cs +++ b/src/Compilers/Core/Portable/Text/ChangedText.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Text; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -278,8 +279,10 @@ protected override TextLineCollection GetLinesCore() } // compute line starts given changes and line starts already computed from previous text - var lineStarts = ArrayBuilder.GetInstance(oldLineInfo.Count); - lineStarts.Add(0); + var lineStarts = new SegmentedList(capacity: oldLineInfo.Count) + { + 0 + }; // position in the original document var position = 0; @@ -299,7 +302,7 @@ protected override TextLineCollection GetLinesCore() { // remove last added line start (it was due to previous CR) // a new line start including the LF will be added next - lineStarts.RemoveLast(); + lineStarts.RemoveAt(lineStarts.Count - 1); } var lps = oldLineInfo.GetLinePositionSpan(TextSpan.FromBounds(position, change.Span.Start)); @@ -328,7 +331,7 @@ protected override TextLineCollection GetLinesCore() { // remove last added line start (it was due to previous CR) // a new line start including the LF will be added next - lineStarts.RemoveLast(); + lineStarts.RemoveAt(lineStarts.Count - 1); } // Skip first line (it is always at offset 0 and corresponds to the previous line) @@ -351,7 +354,7 @@ protected override TextLineCollection GetLinesCore() { // remove last added line start (it was due to previous CR) // a new line start including the LF will be added next - lineStarts.RemoveLast(); + lineStarts.RemoveAt(lineStarts.Count - 1); } var lps = oldLineInfo.GetLinePositionSpan(TextSpan.FromBounds(position, oldText.Length)); @@ -361,7 +364,7 @@ protected override TextLineCollection GetLinesCore() } } - return new LineInfo(this, lineStarts.ToArrayAndFree()); + return new LineInfo(this, lineStarts); } internal static class TestAccessor diff --git a/src/Compilers/Core/Portable/Text/LargeText.cs b/src/Compilers/Core/Portable/Text/LargeText.cs index b370fffd8d1ec..293623460eaef 100644 --- a/src/Compilers/Core/Portable/Text/LargeText.cs +++ b/src/Compilers/Core/Portable/Text/LargeText.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; using System.Diagnostics; +using Microsoft.CodeAnalysis.Collections; namespace Microsoft.CodeAnalysis.Text { @@ -232,12 +233,12 @@ protected override TextLineCollection GetLinesCore() return new LineInfo(this, ParseLineStarts()); } - private int[] ParseLineStarts() + private SegmentedList ParseLineStarts() { var position = 0; var index = 0; var lastCr = -1; - var arrayBuilder = ArrayBuilder.GetInstance(); + var list = new SegmentedList(); // The following loop goes through every character in the text. It is highly // performance critical, and thus inlines knowledge about common line breaks @@ -275,7 +276,7 @@ private int[] ParseLineStarts() case '\u2028': case '\u2029': line_break: - arrayBuilder.Add(position); + list.Add(position); position = index; break; } @@ -283,8 +284,8 @@ private int[] ParseLineStarts() } // Create a start for the final line. - arrayBuilder.Add(position); - return arrayBuilder.ToArrayAndFree(); + list.Add(position); + return list; } } } diff --git a/src/Compilers/Core/Portable/Text/SourceText.cs b/src/Compilers/Core/Portable/Text/SourceText.cs index 5e9ad992716a1..4f5421e1c0053 100644 --- a/src/Compilers/Core/Portable/Text/SourceText.cs +++ b/src/Compilers/Core/Portable/Text/SourceText.cs @@ -15,6 +15,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -942,28 +943,28 @@ protected virtual TextLineCollection GetLinesCore() internal sealed class LineInfo : TextLineCollection { private readonly SourceText _text; - private readonly int[] _lineStarts; + private readonly SegmentedList _lineStarts; private int _lastLineNumber; - public LineInfo(SourceText text, int[] lineStarts) + public LineInfo(SourceText text, SegmentedList lineStarts) { _text = text; _lineStarts = lineStarts; } - public override int Count => _lineStarts.Length; + public override int Count => _lineStarts.Count; public override TextLine this[int index] { get { - if (index < 0 || index >= _lineStarts.Length) + if (index < 0 || index >= _lineStarts.Count) { throw new ArgumentOutOfRangeException(nameof(index)); } int start = _lineStarts[index]; - if (index == _lineStarts.Length - 1) + if (index == _lineStarts.Count - 1) { return TextLine.FromSpan(_text, TextSpan.FromBounds(start, _text.Length)); } @@ -989,7 +990,7 @@ public override int IndexOf(int position) var lastLineNumber = _lastLineNumber; if (position >= _lineStarts[lastLineNumber]) { - var limit = Math.Min(_lineStarts.Length, lastLineNumber + 4); + var limit = Math.Min(_lineStarts.Count, lastLineNumber + 4); for (int i = lastLineNumber; i < limit; i++) { if (position < _lineStarts[i]) @@ -1040,16 +1041,18 @@ private void EnumerateChars(Action action) s_charArrayPool.Free(buffer); } - private int[] ParseLineStarts() + private SegmentedList ParseLineStarts() { // Corner case check if (0 == this.Length) { - return new[] { 0 }; + return [0]; } - var lineStarts = ArrayBuilder.GetInstance(); - lineStarts.Add(0); // there is always the first line + var lineStarts = new SegmentedList() + { + 0 // there is always the first line + }; var lastWasCR = false; @@ -1107,7 +1110,7 @@ private int[] ParseLineStarts() } }); - return lineStarts.ToArrayAndFree(); + return lineStarts; } #endregion diff --git a/src/Compilers/Server/VBCSCompilerTests/AnalyzerConsistencyCheckerTests.cs b/src/Compilers/Server/VBCSCompilerTests/AnalyzerConsistencyCheckerTests.cs index 7cdf7cc9c9287..d5d7eeb2fef99 100644 --- a/src/Compilers/Server/VBCSCompilerTests/AnalyzerConsistencyCheckerTests.cs +++ b/src/Compilers/Server/VBCSCompilerTests/AnalyzerConsistencyCheckerTests.cs @@ -65,7 +65,7 @@ private TempFile CreateNetStandardDll(TempDirectory directory, string assemblyNa var comp = CSharpCompilation.Create( assemblyName, sources, - references: NetStandard20.All, + references: NetStandard20.References.All, options: options); var file = directory.CreateFile($"{assemblyName}.dll"); @@ -112,7 +112,7 @@ public void DifferingMvidsDifferentDirectory() var directory = Temp.CreateDirectory(); var assemblyLoader = DefaultAnalyzerAssemblyLoader.CreateNonLockingLoader(directory.CreateDirectory("shadow").Path); - var key = NetStandard20.netstandard.GetAssemblyIdentity().PublicKey; + var key = NetStandard20.References.netstandard.GetAssemblyIdentity().PublicKey; var mvidAlpha1 = CreateNetStandardDll(directory.CreateDirectory("mvid1"), "MvidAlpha", "1.0.0.0", key, "class C { }"); var mvidAlpha2 = CreateNetStandardDll(directory.CreateDirectory("mvid2"), "MvidAlpha", "1.0.0.0", key, "class D { }"); @@ -137,7 +137,7 @@ public void DifferingMvidsSameDirectory() var directory = Temp.CreateDirectory(); var assemblyLoader = DefaultAnalyzerAssemblyLoader.CreateNonLockingLoader(directory.CreateDirectory("shadow").Path); - var key = NetStandard20.netstandard.GetAssemblyIdentity().PublicKey; + var key = NetStandard20.References.netstandard.GetAssemblyIdentity().PublicKey; var mvidAlpha1 = CreateNetStandardDll(directory, "MvidAlpha", "1.0.0.0", key, "class C { }"); var result = AnalyzerConsistencyChecker.Check( @@ -168,7 +168,7 @@ public void DifferingMvidsSameDirectory() public void LoadingLibraryFromCompiler() { var directory = Temp.CreateDirectory(); - _ = CreateNetStandardDll(directory, "System.Memory", "2.0.0.0", NetStandard20.netstandard.GetAssemblyIdentity().PublicKey); + _ = CreateNetStandardDll(directory, "System.Memory", "2.0.0.0", NetStandard20.References.netstandard.GetAssemblyIdentity().PublicKey); // This test must use the DefaultAnalyzerAssemblyLoader as we want assembly binding redirects // to take affect here. @@ -226,7 +226,7 @@ public void AssemblyLoadException() public void LoadingSimpleLibrary() { var directory = Temp.CreateDirectory(); - var key = NetStandard20.netstandard.GetAssemblyIdentity().PublicKey; + var key = NetStandard20.References.netstandard.GetAssemblyIdentity().PublicKey; var compFile = CreateNetStandardDll(directory, "netstandardRef", "1.0.0.0", key); var analyzerReferences = ImmutableArray.Create(new CommandLineAnalyzerReference(compFile.Path)); diff --git a/src/Compilers/Shared/BuildServerConnection.cs b/src/Compilers/Shared/BuildServerConnection.cs index c7b1ab3fb10d0..93c34187a9a8e 100644 --- a/src/Compilers/Shared/BuildServerConnection.cs +++ b/src/Compilers/Shared/BuildServerConnection.cs @@ -164,7 +164,7 @@ internal static Task RunServerBuildRequestAsync( buildRequest, pipeName, timeoutOverride: null, - tryCreateServerFunc: (pipeName, logger) => TryCreateServer(clientDirectory, pipeName, logger), + tryCreateServerFunc: (pipeName, logger) => TryCreateServer(clientDirectory, pipeName, logger, out int _), logger, cancellationToken); @@ -446,8 +446,9 @@ internal static (string processFilePath, string commandLineArguments, string too /// compiler server process was successful, it does not state whether the server successfully /// started or not (it could crash on startup). /// - internal static bool TryCreateServer(string clientDirectory, string pipeName, ICompilerServerLogger logger) + internal static bool TryCreateServer(string clientDirectory, string pipeName, ICompilerServerLogger logger, out int processId) { + processId = 0; var serverInfo = GetServerProcessInfo(clientDirectory, pipeName); if (!File.Exists(serverInfo.toolFilePath)) @@ -492,6 +493,7 @@ internal static bool TryCreateServer(string clientDirectory, string pipeName, IC logger.Log("Successfully created process with process id {0}", processInfo.dwProcessId); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); + processId = processInfo.dwProcessId; } else { @@ -515,8 +517,15 @@ internal static bool TryCreateServer(string clientDirectory, string pipeName, IC CreateNoWindow = true }; - Process.Start(startInfo); - return true; + if (Process.Start(startInfo) is { } process) + { + processId = process.Id; + return true; + } + else + { + return false; + } } catch { diff --git a/src/Compilers/Test/Core/AssemblyLoadTestFixture.cs b/src/Compilers/Test/Core/AssemblyLoadTestFixture.cs index 1dc56963a68c2..b86840383740d 100644 --- a/src/Compilers/Test/Core/AssemblyLoadTestFixture.cs +++ b/src/Compilers/Test/Core/AssemblyLoadTestFixture.cs @@ -522,9 +522,9 @@ private static string GenerateDll(string assemblyName, TempDirectory directory, syntaxTrees: new SyntaxTree[] { SyntaxFactory.ParseSyntaxTree(SourceText.From(csSource, encoding: null, SourceHashAlgorithms.Default)) }, references: (new MetadataReference[] { - NetStandard20.mscorlib, - NetStandard20.netstandard, - NetStandard20.SystemRuntime + NetStandard20.References.mscorlib, + NetStandard20.References.netstandard, + NetStandard20.References.SystemRuntime }).Concat(additionalReferences), options: options); diff --git a/src/Compilers/Test/Core/Diagnostics/DiagnosticDescription.cs b/src/Compilers/Test/Core/Diagnostics/DiagnosticDescription.cs index 01817d73a198a..89d683b133610 100644 --- a/src/Compilers/Test/Core/Diagnostics/DiagnosticDescription.cs +++ b/src/Compilers/Test/Core/Diagnostics/DiagnosticDescription.cs @@ -6,17 +6,18 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; -using Roslyn.Test.Utilities; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.CSharp; -using System.Collections.Immutable; namespace Microsoft.CodeAnalysis.Test.Utilities { @@ -518,7 +519,7 @@ public static string GetAssertText(DiagnosticDescription[] expected, IEnumerable for (i = 0; e.MoveNext(); i++) { Diagnostic d = e.Current; - string message = d.ToString(); + string message = d.ToString(CultureInfo.InvariantCulture); if (Regex.Match(message, @"{\d+}").Success) { Assert.True(false, "Diagnostic messages should never contain unsubstituted placeholders.\n " + message); @@ -533,7 +534,7 @@ public static string GetAssertText(DiagnosticDescription[] expected, IEnumerable { Indent(assertText, indentDepth); assertText.Append("// "); - assertText.AppendLine(d.ToString()); + assertText.AppendLine(message); var l = d.Location; if (l.IsInSource) { diff --git a/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj b/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj index bdfcfbef906ef..76c9aeab86bb0 100644 --- a/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj +++ b/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj @@ -70,6 +70,7 @@ + diff --git a/src/Compilers/Test/Core/Platform/Desktop/TestHelpers.cs b/src/Compilers/Test/Core/Platform/Desktop/TestHelpers.cs index fc864457cf320..c00cc71683579 100644 --- a/src/Compilers/Test/Core/Platform/Desktop/TestHelpers.cs +++ b/src/Compilers/Test/Core/Platform/Desktop/TestHelpers.cs @@ -83,9 +83,9 @@ public class TestAnalyzer : DiagnosticAnalyzer new SyntaxTree[] { SyntaxFactory.ParseSyntaxTree(SourceText.From(analyzerSource, encoding: null, SourceHashAlgorithms.Default)) }, new MetadataReference[] { - NetStandard20.mscorlib, - NetStandard20.netstandard, - NetStandard20.SystemRuntime, + NetStandard20.References.mscorlib, + NetStandard20.References.netstandard, + NetStandard20.References.SystemRuntime, MetadataReference.CreateFromFile(immutable.Path), MetadataReference.CreateFromFile(analyzer.Path) }, diff --git a/src/Compilers/Test/Core/TargetFrameworkUtil.cs b/src/Compilers/Test/Core/TargetFrameworkUtil.cs index 6f9ea4264263a..19a0b17c34578 100644 --- a/src/Compilers/Test/Core/TargetFrameworkUtil.cs +++ b/src/Compilers/Test/Core/TargetFrameworkUtil.cs @@ -101,12 +101,12 @@ public enum TargetFramework /// public static class NetCoreApp { - public static ImmutableArray AllReferenceInfos { get; } = ImmutableArray.CreateRange(Net70.References.All); - public static ImmutableArray References { get; } = ImmutableArray.CreateRange(Net70.All); + public static ImmutableArray AllReferenceInfos { get; } = ImmutableArray.CreateRange(Net70.ReferenceInfos.All); + public static ImmutableArray References { get; } = ImmutableArray.CreateRange(Net70.References.All); - public static PortableExecutableReference netstandard { get; } = Net70.netstandard; - public static PortableExecutableReference mscorlib { get; } = Net70.mscorlib; - public static PortableExecutableReference SystemRuntime { get; } = Net70.SystemRuntime; + public static PortableExecutableReference netstandard { get; } = Net70.References.netstandard; + public static PortableExecutableReference mscorlib { get; } = Net70.References.mscorlib; + public static PortableExecutableReference SystemRuntime { get; } = Net70.References.SystemRuntime; } /// @@ -124,7 +124,7 @@ public static class NetFramework /// public static ImmutableArray References { get; } = ImmutableArray - .CreateRange(Net461.All) + .CreateRange(Net461.References.All) .Add(NetFx.ValueTuple.tuplelib); /// @@ -136,19 +136,19 @@ public static class NetFramework /// public static ImmutableArray Standard { get; } = ImmutableArray.Create( - Net461.mscorlib, - Net461.System, - Net461.SystemCore, + Net461.References.mscorlib, + Net461.References.System, + Net461.References.SystemCore, NetFx.ValueTuple.tuplelib, - Net461.SystemRuntime); - - public static PortableExecutableReference mscorlib { get; } = Net461.mscorlib; - public static PortableExecutableReference System { get; } = Net461.System; - public static PortableExecutableReference SystemRuntime { get; } = Net461.SystemRuntime; - public static PortableExecutableReference SystemCore { get; } = Net461.SystemCore; - public static PortableExecutableReference SystemThreadingTasks { get; } = Net461.SystemThreadingTasks; - public static PortableExecutableReference MicrosoftCSharp { get; } = Net461.MicrosoftCSharp; - public static PortableExecutableReference MicrosoftVisualBasic { get; } = Net461.MicrosoftVisualBasic; + Net461.References.SystemRuntime); + + public static PortableExecutableReference mscorlib { get; } = Net461.References.mscorlib; + public static PortableExecutableReference System { get; } = Net461.References.System; + public static PortableExecutableReference SystemRuntime { get; } = Net461.References.SystemRuntime; + public static PortableExecutableReference SystemCore { get; } = Net461.References.SystemCore; + public static PortableExecutableReference SystemThreadingTasks { get; } = Net461.References.SystemThreadingTasks; + public static PortableExecutableReference MicrosoftCSharp { get; } = Net461.References.MicrosoftCSharp; + public static PortableExecutableReference MicrosoftVisualBasic { get; } = Net461.References.MicrosoftVisualBasic; } public static class TargetFrameworkUtil @@ -168,24 +168,108 @@ public static class TargetFrameworkUtil * for a TypeLoadException are missing important information for resolving problems if/when they occur. * https://github.com/dotnet/roslyn/issues/25961 */ + public static ImmutableArray WinRTReferences => + [ + .. TestBase.WinRtRefs + ]; + public static ImmutableArray MinimalReferences => + [ + TestBase.MinCorlibRef + ]; + public static ImmutableArray MinimalAsyncReferences => + [ + TestBase.MinAsyncCorlibRef + ]; + public static ImmutableArray Mscorlib45ExtendedReferences => + [ + Net451.mscorlib, + Net451.System, + Net451.SystemCore, + TestBase.ValueTupleRef, + Net451.SystemRuntime + ]; + public static ImmutableArray Mscorlib46ExtendedReferences => + [ + Net461.References.mscorlib, + Net461.References.System, + Net461.References.SystemCore, + TestBase.ValueTupleRef, + Net461.References.SystemRuntime + ]; + /* + * ⚠ Dev note ⚠: TestBase properties end here. + */ - public static ImmutableArray Mscorlib40References => ImmutableArray.Create(Net40.mscorlib); - public static ImmutableArray Mscorlib40ExtendedReferences => ImmutableArray.Create(Net40.mscorlib, Net40.System, Net40.SystemCore); - public static ImmutableArray Mscorlib40andSystemCoreReferences => ImmutableArray.Create(Net40.mscorlib, Net40.SystemCore); - public static ImmutableArray Mscorlib40andVBRuntimeReferences => ImmutableArray.Create(Net40.mscorlib, Net40.System, Net40.MicrosoftVisualBasic); - public static ImmutableArray Mscorlib45References => ImmutableArray.Create(Net451.mscorlib); - public static ImmutableArray Mscorlib45ExtendedReferences => ImmutableArray.Create(Net451.mscorlib, Net451.System, Net451.SystemCore, TestBase.ValueTupleRef, Net451.SystemRuntime); - public static ImmutableArray Mscorlib45AndCSharpReferences => ImmutableArray.Create(Net451.mscorlib, Net451.SystemCore, Net451.MicrosoftCSharp); - public static ImmutableArray Mscorlib45AndVBRuntimeReferences => ImmutableArray.Create(Net451.mscorlib, Net451.System, Net451.MicrosoftVisualBasic); - public static ImmutableArray Mscorlib46References => ImmutableArray.Create(Net461.mscorlib); - public static ImmutableArray Mscorlib46ExtendedReferences => ImmutableArray.Create(Net461.mscorlib, Net461.System, Net461.SystemCore, TestBase.ValueTupleRef, Net461.SystemRuntime); - public static ImmutableArray Mscorlib461References => ImmutableArray.Create(Net461.mscorlib); - public static ImmutableArray Mscorlib461ExtendedReferences => ImmutableArray.Create(Net461.mscorlib, Net461.System, Net461.SystemCore, NetFx.ValueTuple.tuplelib, Net461.SystemRuntime); - public static ImmutableArray NetStandard20References => ImmutableArray.Create(NetStandard20.netstandard, NetStandard20.mscorlib, NetStandard20.SystemRuntime, NetStandard20.SystemCore, NetStandard20.SystemDynamicRuntime, NetStandard20.SystemLinq, NetStandard20.SystemLinqExpressions); - public static ImmutableArray WinRTReferences => ImmutableArray.Create(TestBase.WinRtRefs); - public static ImmutableArray DefaultVbReferences => ImmutableArray.Create(Net451.mscorlib, Net451.System, Net451.SystemCore, Net451.MicrosoftVisualBasic); - public static ImmutableArray MinimalReferences => ImmutableArray.Create(TestBase.MinCorlibRef); - public static ImmutableArray MinimalAsyncReferences => ImmutableArray.Create(TestBase.MinAsyncCorlibRef); + public static ImmutableArray Mscorlib40References { get; } = + [ + Net40.mscorlib + ]; + public static ImmutableArray Mscorlib40ExtendedReferences { get; } = + [ + Net40.mscorlib, + Net40.System, + Net40.SystemCore + ]; + public static ImmutableArray Mscorlib40andSystemCoreReferences { get; } = + [ + Net40.mscorlib, + Net40.SystemCore + ]; + public static ImmutableArray Mscorlib40andVBRuntimeReferences { get; } = + [ + Net40.mscorlib, + Net40.System, + Net40.MicrosoftVisualBasic + ]; + public static ImmutableArray Mscorlib45References { get; } = + [ + Net451.mscorlib + ]; + public static ImmutableArray Mscorlib45AndCSharpReferences { get; } = + [ + Net451.mscorlib, + Net451.SystemCore, + Net451.MicrosoftCSharp + ]; + public static ImmutableArray Mscorlib45AndVBRuntimeReferences { get; } = + [ + Net451.mscorlib, + Net451.System, + Net451.MicrosoftVisualBasic + ]; + public static ImmutableArray Mscorlib46References { get; } = + [ + Net461.References.mscorlib + ]; + public static ImmutableArray Mscorlib461References { get; } = + [ + Net461.References.mscorlib + ]; + public static ImmutableArray Mscorlib461ExtendedReferences { get; } = + [ + Net461.References.mscorlib, + Net461.References.System, + Net461.References.SystemCore, + NetFx.ValueTuple.tuplelib, + Net461.References.SystemRuntime + ]; + public static ImmutableArray NetStandard20References { get; } = + [ + NetStandard20.References.netstandard, + NetStandard20.References.mscorlib, + NetStandard20.References.SystemRuntime, + NetStandard20.References.SystemCore, + NetStandard20.References.SystemDynamicRuntime, + NetStandard20.References.SystemLinq, + NetStandard20.References.SystemLinqExpressions + ]; + public static ImmutableArray DefaultVbReferences { get; } = + [ + Net451.mscorlib, + Net451.System, + Net451.SystemCore, + Net451.MicrosoftVisualBasic + ]; #if DEBUG @@ -206,7 +290,7 @@ static TargetFrameworkUtil() TargetFramework.NetStandard20 => NetStandard20References, TargetFramework.Net50 => ImmutableArray.CreateRange(LoadDynamicReferences("Net50")), TargetFramework.Net60 => ImmutableArray.CreateRange(LoadDynamicReferences("Net60")), - TargetFramework.NetCoreApp or TargetFramework.Net70 => ImmutableArray.CreateRange(Net70.All), + TargetFramework.NetCoreApp or TargetFramework.Net70 => ImmutableArray.CreateRange(Net70.References.All), TargetFramework.Net80 => ImmutableArray.CreateRange(LoadDynamicReferences("Net80")), TargetFramework.NetFramework => NetFramework.References, TargetFramework.NetLatest => NetLatest, diff --git a/src/Compilers/Test/Core/TestBase.cs b/src/Compilers/Test/Core/TestBase.cs index d4de9c7e3d829..fe864fc970d5f 100644 --- a/src/Compilers/Test/Core/TestBase.cs +++ b/src/Compilers/Test/Core/TestBase.cs @@ -163,7 +163,7 @@ public virtual void Dispose() public static MetadataReference SystemRuntimeSerializationRef_v4_0_30319_17929 => s_systemRuntimeSerializationRef_v4_0_30319_17929.Value; private static readonly Lazy s_systemCoreRef_v46 = new Lazy( - () => AssemblyMetadata.CreateFromImage(Net461.References.SystemCore.ImageBytes).GetReference(display: "System.Core.v4_6_1038_0.dll"), + () => AssemblyMetadata.CreateFromImage(Net461.ReferenceInfos.SystemCore.ImageBytes).GetReference(display: "System.Core.v4_6_1038_0.dll"), LazyThreadSafetyMode.PublicationOnly); public static MetadataReference SystemCoreRef_v46 => s_systemCoreRef_v4_0_30319_17929.Value; @@ -220,7 +220,7 @@ public virtual void Dispose() public static MetadataReference MscorlibRef_v4_0_30316_17626 => Net451.mscorlib; private static readonly Lazy s_mscorlibRef_v46 = new Lazy( - () => AssemblyMetadata.CreateFromImage(Net461.References.mscorlib.ImageBytes).GetReference(display: "mscorlib.v4_6_1038_0.dll", filePath: @"Z:\FxReferenceAssembliesUri"), + () => AssemblyMetadata.CreateFromImage(Net461.ReferenceInfos.mscorlib.ImageBytes).GetReference(display: "mscorlib.v4_6_1038_0.dll", filePath: @"Z:\FxReferenceAssembliesUri"), LazyThreadSafetyMode.PublicationOnly); public static MetadataReference MscorlibRef_v46 => s_mscorlibRef_v46.Value; @@ -267,7 +267,7 @@ public virtual void Dispose() public static MetadataReference SystemRef => s_systemRef.Value; private static readonly Lazy s_systemRef_v46 = new Lazy( - () => AssemblyMetadata.CreateFromImage(Net461.References.System.ImageBytes).GetReference(display: "System.v4_6_1038_0.dll"), + () => AssemblyMetadata.CreateFromImage(Net461.ReferenceInfos.System.ImageBytes).GetReference(display: "System.v4_6_1038_0.dll"), LazyThreadSafetyMode.PublicationOnly); public static MetadataReference SystemRef_v46 => s_systemRef_v46.Value; diff --git a/src/Compilers/Test/Core/Traits/Traits.cs b/src/Compilers/Test/Core/Traits/Traits.cs index d29b1722e8107..f08f2fdc25b5d 100644 --- a/src/Compilers/Test/Core/Traits/Traits.cs +++ b/src/Compilers/Test/Core/Traits/Traits.cs @@ -125,6 +125,7 @@ public static class Features public const string CodeActionsMakeTypePartial = "CodeActions.MakeTypePartial"; public const string CodeActionsMakeFieldReadonly = "CodeActions.MakeFieldReadonly"; public const string CodeActionsMakeLocalFunctionStatic = "CodeActions.MakeLocalFunctionStatic"; + public const string CodeActionsMakeAnonymousFunctionStatic = "CodeActions.MakeAnonymousFunctionStatic"; public const string CodeActionsMakeMemberRequired = "CodeActions.MakeMemberRequired"; public const string CodeActionsMakeMethodAsynchronous = "CodeActions.MakeMethodAsynchronous"; public const string CodeActionsMakeMethodSynchronous = "CodeActions.MakeMethodSynchronous"; diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index 7e37c631b3766..8388968457fb4 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -1204,7 +1204,7 @@ public static CSharpCompilation CreateCompilationWithTasksExtensions( else { allReferences = TargetFrameworkUtil.Mscorlib461ExtendedReferences; - allReferences = allReferences.Concat(new[] { Net461.SystemThreadingTasks, SystemThreadingTasksExtensions.PortableLib }); + allReferences = allReferences.Concat(new[] { Net461.References.SystemThreadingTasks, SystemThreadingTasksExtensions.PortableLib }); } if (references != null) diff --git a/src/Compilers/VisualBasic/Portable/Binding/BinderFactory.BinderFactoryVisitor.vb b/src/Compilers/VisualBasic/Portable/Binding/BinderFactory.BinderFactoryVisitor.vb index d7e1b1d30bc34..6539ae3ebd236 100644 --- a/src/Compilers/VisualBasic/Portable/Binding/BinderFactory.BinderFactoryVisitor.vb +++ b/src/Compilers/VisualBasic/Portable/Binding/BinderFactory.BinderFactoryVisitor.vb @@ -2,27 +2,26 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic Partial Friend Class BinderFactory - Private NotInheritable Class BinderFactoryVisitor + Friend NotInheritable Class BinderFactoryVisitor Inherits VisualBasicSyntaxVisitor(Of Binder) Private _position As Integer - Private ReadOnly _factory As BinderFactory + Private _factory As BinderFactory - Public Sub New(factory As BinderFactory) + Public Sub Initialize(factory As BinderFactory, position As Integer) Me._factory = factory + Me._position = position End Sub - Friend WriteOnly Property Position As Integer - Set(value As Integer) - Me._position = value - End Set - End Property + Public Sub Clear() + _factory = Nothing + _position = 0 + End Sub Public Overrides Function VisitXmlCrefAttribute(node As XmlCrefAttributeSyntax) As Binder Dim trivia As StructuredTriviaSyntax = node.EnclosingStructuredTrivia diff --git a/src/Compilers/VisualBasic/Portable/Binding/BinderFactory.vb b/src/Compilers/VisualBasic/Portable/Binding/BinderFactory.vb index 7992de3ed8202..6cf92a9196215 100644 --- a/src/Compilers/VisualBasic/Portable/Binding/BinderFactory.vb +++ b/src/Compilers/VisualBasic/Portable/Binding/BinderFactory.vb @@ -3,11 +3,8 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Concurrent -Imports System.Collections.Generic Imports System.Collections.Immutable -Imports System.Threading Imports Microsoft.CodeAnalysis.PooledObjects -Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -30,6 +27,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ' a NodeUsage to sub-distinguish binders associated with nodes. Each kind of syntax node must have its ' associated usage value(s), because the usage is used when creating the binder (if not found in the cache). Private ReadOnly _cache As ConcurrentDictionary(Of ValueTuple(Of VisualBasicSyntaxNode, Byte), Binder) + + Private Shared ReadOnly s_binderFactoryVisitorPool As ObjectPool(Of BinderFactoryVisitor) = New ObjectPool(Of BinderFactoryVisitor)(Function() New BinderFactoryVisitor()) Private ReadOnly _binderFactoryVisitorPool As ObjectPool(Of BinderFactoryVisitor) Private ReadOnly Property InScript As Boolean @@ -38,28 +37,38 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property - Public Sub New(sourceModule As SourceModuleSymbol, tree As SyntaxTree) + Public Sub New(sourceModule As SourceModuleSymbol, tree As SyntaxTree, Optional binderFactoryVisitorPoolOpt As ObjectPool(Of BinderFactoryVisitor) = Nothing) Me._sourceModule = sourceModule Me._tree = tree Me._cache = New ConcurrentDictionary(Of ValueTuple(Of VisualBasicSyntaxNode, Byte), Binder) - - Me._binderFactoryVisitorPool = New ObjectPool(Of BinderFactoryVisitor)(Function() New BinderFactoryVisitor(Me)) + Me._binderFactoryVisitorPool = If(binderFactoryVisitorPoolOpt, s_binderFactoryVisitorPool) End Sub Private Function MakeBinder(node As SyntaxNode, position As Integer) As Binder If SyntaxFacts.InSpanOrEffectiveTrailingOfNode(node, position) OrElse node.Kind = SyntaxKind.CompilationUnit Then - Dim visitor = _binderFactoryVisitorPool.Allocate() - visitor.Position = position + Dim visitor = GetBinderFactoryVisitor(position) Dim result = visitor.Visit(node) - _binderFactoryVisitorPool.Free(visitor) + ClearBinderFactoryVisitor(visitor) Return result End If Return Nothing End Function + Private Function GetBinderFactoryVisitor(position As Integer) As BinderFactoryVisitor + Dim visitor = _binderFactoryVisitorPool.Allocate() + visitor.Initialize(Me, position) + + Return visitor + End Function + + Private Sub ClearBinderFactoryVisitor(visitor As BinderFactoryVisitor) + visitor.Clear() + _binderFactoryVisitorPool.Free(visitor) + End Sub + ' Get binder for interior of a namespace block Public Function GetNamespaceBinder(node As NamespaceBlockSyntax) As Binder Return GetBinderForNodeAndUsage(node, NodeUsage.NamespaceBlockInterior, node.Parent, node.SpanStart) diff --git a/src/Compilers/VisualBasic/Portable/Binding/Binder_Expressions.vb b/src/Compilers/VisualBasic/Portable/Binding/Binder_Expressions.vb index f4f2df71c9200..3d93cd5b469d9 100644 --- a/src/Compilers/VisualBasic/Portable/Binding/Binder_Expressions.vb +++ b/src/Compilers/VisualBasic/Portable/Binding/Binder_Expressions.vb @@ -694,7 +694,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ReportDiagnostic(diagnostics, node.Type, ErrorFactory.ErrorInfo(ERRID.ERR_VoidArrayDisallowed)) End If - Return New BoundGetType(node, typeExpression, GetWellKnownType(WellKnownType.System_Type, node, diagnostics)) + Return New BoundGetType(node, typeExpression, getTypeFromHandle:=Nothing, GetWellKnownType(WellKnownType.System_Type, node, diagnostics)) End Function Private Function BindNameOfExpression(node As NameOfExpressionSyntax, diagnostics As BindingDiagnosticBag) As BoundExpression diff --git a/src/Compilers/VisualBasic/Portable/BoundTree/BoundCall.vb b/src/Compilers/VisualBasic/Portable/BoundTree/BoundCall.vb index abae4217e3566..1db8f6060bd12 100644 --- a/src/Compilers/VisualBasic/Portable/BoundTree/BoundCall.vb +++ b/src/Compilers/VisualBasic/Portable/BoundTree/BoundCall.vb @@ -113,7 +113,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic If isLateBound Then Debug.Assert(type.IsObjectType) ElseIf Not isOperator Then - Debug.Assert(type.IsSameTypeIgnoringAll(signatureType)) + Debug.Assert(type.IsErrorType() OrElse type.IsSameTypeIgnoringAll(signatureType)) ElseIf Not isLifted.HasValue Then If type.IsSameTypeIgnoringAll(signatureType) Then isLifted = False diff --git a/src/Compilers/VisualBasic/Portable/BoundTree/BoundNodes.xml b/src/Compilers/VisualBasic/Portable/BoundTree/BoundNodes.xml index 53fa1217665e8..20716accb919f 100644 --- a/src/Compilers/VisualBasic/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/VisualBasic/Portable/BoundTree/BoundNodes.xml @@ -182,6 +182,8 @@ types. --> + + diff --git a/src/Compilers/VisualBasic/Portable/CodeGen/EmitExpression.vb b/src/Compilers/VisualBasic/Portable/CodeGen/EmitExpression.vb index 3847b066b9825..c02c178c4e173 100644 --- a/src/Compilers/VisualBasic/Portable/CodeGen/EmitExpression.vb +++ b/src/Compilers/VisualBasic/Portable/CodeGen/EmitExpression.vb @@ -2227,7 +2227,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen EmitSymbolToken(type, boundTypeOfOperator.SourceType.Syntax) _builder.EmitOpCode(ILOpCode.Call, stackAdjustment:=0) 'argument off, return value on - Dim getTypeMethod = DirectCast(Me._module.Compilation.GetWellKnownTypeMember(WellKnownMember.System_Type__GetTypeFromHandle), MethodSymbol) + Dim getTypeMethod = boundTypeOfOperator.GetTypeFromHandle Debug.Assert(getTypeMethod IsNot Nothing) ' Should have been checked during binding EmitSymbolToken(getTypeMethod, boundTypeOfOperator.Syntax) EmitPopIfUnused(used) @@ -2270,8 +2270,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen EmitSymbolToken(method, node.Syntax) If node.GetMethodFromHandle.ParameterCount = 1 Then + Debug.Assert(Not method.ContainingType.IsGenericType AndAlso Not method.ContainingType.IsAnonymousType) _builder.EmitOpCode(ILOpCode.Call, stackAdjustment:=0) ' argument off, return value on Else + Debug.Assert(method.ContainingType.IsGenericType OrElse method.ContainingType.IsAnonymousType) Debug.Assert(node.GetMethodFromHandle.ParameterCount = 2) _builder.EmitOpCode(ILOpCode.Ldtoken) diff --git a/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb b/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb index c8864636d4ef5..0a39a76c105d9 100644 --- a/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb +++ b/src/Compilers/VisualBasic/Portable/Generated/BoundNodes.xml.Generated.vb @@ -846,13 +846,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Partial Friend NotInheritable Class BoundGetType Inherits BoundExpression - Public Sub New(syntax As SyntaxNode, sourceType As BoundTypeExpression, type As TypeSymbol, Optional hasErrors As Boolean = False) + Public Sub New(syntax As SyntaxNode, sourceType As BoundTypeExpression, getTypeFromHandle As MethodSymbol, type As TypeSymbol, Optional hasErrors As Boolean = False) MyBase.New(BoundKind.GetType, syntax, type, hasErrors OrElse sourceType.NonNullAndHasErrors()) Debug.Assert(sourceType IsNot Nothing, "Field 'sourceType' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") Debug.Assert(type IsNot Nothing, "Field 'type' cannot be null (use Null=""allow"" in BoundNodes.xml to remove this check)") Me._SourceType = sourceType + Me._GetTypeFromHandle = getTypeFromHandle End Sub @@ -863,14 +864,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property + Private ReadOnly _GetTypeFromHandle As MethodSymbol + Public ReadOnly Property GetTypeFromHandle As MethodSymbol + Get + Return _GetTypeFromHandle + End Get + End Property + Public Overrides Function Accept(visitor as BoundTreeVisitor) As BoundNode Return visitor.VisitGetType(Me) End Function - Public Function Update(sourceType As BoundTypeExpression, type As TypeSymbol) As BoundGetType - If sourceType IsNot Me.SourceType OrElse type IsNot Me.Type Then - Dim result = New BoundGetType(Me.Syntax, sourceType, type, Me.HasErrors) + Public Function Update(sourceType As BoundTypeExpression, getTypeFromHandle As MethodSymbol, type As TypeSymbol) As BoundGetType + If sourceType IsNot Me.SourceType OrElse getTypeFromHandle IsNot Me.GetTypeFromHandle OrElse type IsNot Me.Type Then + Dim result = New BoundGetType(Me.Syntax, sourceType, getTypeFromHandle, type, Me.HasErrors) result.CopyAttributes(Me) Return result End If @@ -12125,7 +12133,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Overrides Function VisitGetType(node As BoundGetType) As BoundNode Dim sourceType As BoundTypeExpression = DirectCast(Me.Visit(node.SourceType), BoundTypeExpression) Dim type as TypeSymbol = Me.VisitType(node.Type) - Return node.Update(sourceType, type) + Return node.Update(sourceType, node.GetTypeFromHandle, type) End Function Public Overrides Function VisitFieldInfo(node As BoundFieldInfo) As BoundNode @@ -13241,6 +13249,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Overrides Function VisitGetType(node As BoundGetType, arg As Object) As TreeDumperNode Return New TreeDumperNode("[getType]", Nothing, New TreeDumperNode() { New TreeDumperNode("sourceType", Nothing, new TreeDumperNode() {Visit(node.SourceType, Nothing)}), + New TreeDumperNode("getTypeFromHandle", node.GetTypeFromHandle, Nothing), New TreeDumperNode("type", node.Type, Nothing) }) End Function diff --git a/src/Compilers/VisualBasic/Portable/Lowering/ExpressionLambdaRewriter/ExpressionLambdaRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/ExpressionLambdaRewriter/ExpressionLambdaRewriter.vb index 887181c459208..4cbdb9822001b 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/ExpressionLambdaRewriter/ExpressionLambdaRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/ExpressionLambdaRewriter/ExpressionLambdaRewriter.vb @@ -265,7 +265,7 @@ lSelect: #If DEBUG Then If node.Kind = BoundKind.GetType Then Dim gt = DirectCast(node, BoundGetType) - node = gt.Update(gt.SourceType.MemberwiseClone(Of BoundTypeExpression)(), gt.Type) + node = gt.Update(gt.SourceType.MemberwiseClone(Of BoundTypeExpression)(), gt.GetTypeFromHandle, gt.Type) Else node = node.MemberwiseClone(Of BoundExpression)() End If diff --git a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter.vb index c23e8a8588636..e6d337367a2a8 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter.vb @@ -880,14 +880,27 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Function Public Overrides Function VisitGetType(node As BoundGetType) As BoundNode + Debug.Assert(node.Type.ExtendedSpecialType = InternalSpecialType.System_Type OrElse + TypeSymbol.Equals(node.Type, Compilation.GetWellKnownType(WellKnownType.System_Type), TypeCompareKind.AllIgnoreOptionsForVB)) + Dim result = DirectCast(MyBase.VisitGetType(node), BoundGetType) ' Emit needs this method. - If Not TryGetWellknownMember(Of MethodSymbol)(Nothing, WellKnownMember.System_Type__GetTypeFromHandle, node.Syntax) Then - Return New BoundGetType(result.Syntax, result.SourceType, result.Type, hasErrors:=True) + Dim tryGetResult As Boolean + Dim getTypeFromHandle As MethodSymbol = Nothing + + If node.Type.ExtendedSpecialType = InternalSpecialType.System_Type Then + tryGetResult = TryGetSpecialMember(Of MethodSymbol)(getTypeFromHandle, SpecialMember.System_Type__GetTypeFromHandle, node.Syntax) + Else + tryGetResult = TryGetWellknownMember(Of MethodSymbol)(getTypeFromHandle, WellKnownMember.System_Type__GetTypeFromHandle, node.Syntax) End If - Return result + If Not tryGetResult Then + Return New BoundGetType(result.Syntax, result.SourceType, getTypeFromHandle:=Nothing, result.Type, hasErrors:=True) + End If + + Debug.Assert(TypeSymbol.Equals(result.Type, getTypeFromHandle.ReturnType, TypeCompareKind.AllIgnoreOptionsForVB)) + Return New BoundGetType(result.Syntax, result.SourceType, getTypeFromHandle, result.Type) End Function Public Overrides Function VisitArrayCreation(node As BoundArrayCreation) As BoundNode diff --git a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_AddRemoveHandler.vb b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_AddRemoveHandler.vb index 52e56fe7f20d7..d308857621285 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_AddRemoveHandler.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_AddRemoveHandler.vb @@ -281,7 +281,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic WellKnownMember.System_Runtime_InteropServices_ComAwareEventInfo__AddEventHandler, WellKnownMember.System_Runtime_InteropServices_ComAwareEventInfo__RemoveEventHandler)) If addRemove IsNot Nothing Then - Dim eventInfo = factory.[New](ctor, factory.Typeof([event].ContainingType, factory.WellKnownType(WellKnownType.System_Type)), factory.Literal([event].MetadataName)) + Dim eventInfo = factory.[New](ctor, factory.Typeof([event].ContainingType, ctor.Parameters(0).Type), factory.Literal([event].MetadataName)) result = factory.Call(eventInfo, addRemove, Convert(factory, addRemove.Parameters(0).Type, receiver.MakeRValue()), diff --git a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_LateBindingHelpers.vb b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_LateBindingHelpers.vb index d760c6acef391..e987c48d5c532 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_LateBindingHelpers.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_LateBindingHelpers.vb @@ -364,8 +364,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic WellKnownMember.Microsoft_VisualBasic_CompilerServices_Conversions__ChangeType, syntax) Then ' value = ChangeType(value, GetType(targetType)) - - Dim getTypeExpr = New BoundGetType(syntax, New BoundTypeExpression(syntax, targetType), changeTypeMethod.Parameters(1).Type) + Dim factory As New SyntheticBoundNodeFactory(_topMethod, _currentMethodOrLambda, syntax, _compilationState, _diagnostics) + Dim getTypeExpr = factory.Typeof(targetType, changeTypeMethod.Parameters(1).Type) 'TODO: should we suppress object clone here? Dev11 does not. value = New BoundCall(syntax, @@ -933,12 +933,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return New BoundLiteral(node, ConstantValue.Create(value), booleanType) End Function - Private Shared Function MakeGetTypeExpression(node As SyntaxNode, + Private Function MakeGetTypeExpression(node As SyntaxNode, type As TypeSymbol, - typeType As TypeSymbol) As BoundGetType + typeType As TypeSymbol) As BoundExpression - Dim typeExpr = New BoundTypeExpression(node, type) - Return New BoundGetType(node, typeExpr, typeType) + Dim factory As New SyntheticBoundNodeFactory(_topMethod, _currentMethodOrLambda, node, _compilationState, _diagnostics) + Return factory.Typeof(type, typeType) End Function Private Function MakeArrayOfGetTypeExpressions(node As SyntaxNode, diff --git a/src/Compilers/VisualBasic/Portable/Lowering/SyntheticBoundNodeFactory.vb b/src/Compilers/VisualBasic/Portable/Lowering/SyntheticBoundNodeFactory.vb index 61c7270828294..57598e4c1b7a6 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/SyntheticBoundNodeFactory.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/SyntheticBoundNodeFactory.vb @@ -844,7 +844,20 @@ nextm: End Function Public Function [Typeof](typeSym As TypeSymbol, systemTypeSymbol As TypeSymbol) As BoundExpression - Dim boundNode = New BoundGetType(_syntax, Type(typeSym), systemTypeSymbol) + Debug.Assert(systemTypeSymbol.ExtendedSpecialType = InternalSpecialType.System_Type OrElse + systemTypeSymbol.Equals(Compilation.GetWellKnownType(CodeAnalysis.WellKnownType.System_Type), TypeCompareKind.AllIgnoreOptionsForVB)) + + Dim getTypeFromHandle As MethodSymbol + + If systemTypeSymbol.ExtendedSpecialType = InternalSpecialType.System_Type Then + getTypeFromHandle = DirectCast(SpecialMember(CodeAnalysis.SpecialMember.System_Type__GetTypeFromHandle), MethodSymbol) + Else + getTypeFromHandle = WellKnownMember(Of MethodSymbol)(CodeAnalysis.WellKnownMember.System_Type__GetTypeFromHandle) + End If + + Debug.Assert(getTypeFromHandle Is Nothing OrElse + TypeSymbol.Equals(systemTypeSymbol, getTypeFromHandle.ReturnType, TypeCompareKind.AllIgnoreOptionsForVB)) + Dim boundNode = New BoundGetType(_syntax, Type(typeSym), getTypeFromHandle, systemTypeSymbol, hasErrors:=getTypeFromHandle Is Nothing) boundNode.SetWasCompilerGenerated() Return boundNode End Function @@ -874,7 +887,7 @@ nextm: End Function Public Function MethodInfo(method As MethodSymbol, systemReflectionMethodInfo As TypeSymbol) As BoundExpression - Debug.Assert(systemReflectionMethodInfo.Equals(Compilation.GetSpecialType(InternalSpecialType.System_Reflection_MethodInfo), TypeCompareKind.AllIgnoreOptionsForVB) OrElse + Debug.Assert(systemReflectionMethodInfo.ExtendedSpecialType = InternalSpecialType.System_Reflection_MethodInfo OrElse systemReflectionMethodInfo.Equals(Compilation.GetWellKnownType(CodeAnalysis.WellKnownType.System_Reflection_MethodInfo), TypeCompareKind.AllIgnoreOptionsForVB) OrElse systemReflectionMethodInfo.Equals(Compilation.GetWellKnownType(CodeAnalysis.WellKnownType.System_Reflection_ConstructorInfo), TypeCompareKind.AllIgnoreOptionsForVB)) diff --git a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbolExtensions.vb b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbolExtensions.vb index 07872e550a8de..cc6b75b7ee6ad 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbolExtensions.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbolExtensions.vb @@ -73,7 +73,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ''' The method Friend Function IsPartialWithoutImplementation(method As MethodSymbol) As Boolean - Dim sourceMethod = TryCast(method, SourceMemberMethodSymbol) + Dim sourceMethod = TryCast(method.OriginalDefinition, SourceMemberMethodSymbol) Return sourceMethod IsNot Nothing AndAlso sourceMethod.IsPartial AndAlso sourceMethod.OtherPartOfPartial Is Nothing End Function diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf index dde635cf5a37c..05f1aa1799b79 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + Hodnota typu System.Threading.Lock není v SyncLock podporována. Zvažte ruční volání metod Enter a Exit ve Zkušebním/Závěrečném bloku. @@ -615,12 +615,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Hodnota typu System.Threading.Lock převedená na jiný typ použije pravděpodobně nezamýšlené zamykání na základě monitorování v příkazu SyncLock. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Hodnota typu System.Threading.Lock převedená na jiný typ použije pravděpodobně nezamýšlené zamykání na základě monitorování v příkazu SyncLock. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf index 92bee2c30c622..985d9a39475a4 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + Ein Wert vom Typ „System.Threading.Lock“ wird in SyncLock nicht unterstützt. Erwägen Sie stattdessen, den manuellen Aufruf der Methoden „Enter“ und „Exit“ in einem Try/Finally-Block. @@ -615,12 +615,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Ein Wert vom Typ „System.Threading.Lock“, der in einen anderen Typ konvertiert wurde, verwendet wahrscheinlich eine unbeabsichtigte monitorbasierte Sperrung in der SyncLock-Anweisung. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Ein Wert vom Typ „System.Threading.Lock“, der in einen anderen Typ konvertiert wurde, verwendet wahrscheinlich eine unbeabsichtigte monitorbasierte Sperrung in der SyncLock-Anweisung. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf index 4533a1ed53233..b4221ac4cce5e 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + SyncLock no admite un valor de tipo 'System.Threading.Lock'. Considere la posibilidad de llamar manualmente a los métodos 'Enter' y 'Exit' en un bloque Try/Finally en su lugar. @@ -615,12 +615,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Un valor de tipo 'System.Threading.Lock' convertido a otro tipo probablemente usará un bloqueo basado en monitor no deseado en la instrucción SyncLock. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Un valor de tipo 'System.Threading.Lock' convertido a otro tipo probablemente usará un bloqueo basado en monitor no deseado en la instrucción SyncLock. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf index fcac41bae73fe..e44fe48dd3e95 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + Une valeur de type « System.Threading.Lock » n’est pas prise en charge dans SyncLock. Appelez manuellement les méthodes « Enter » et « Exit » dans un bloc Try/Finally à la place. @@ -615,12 +615,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Une valeur de type « System.Threading.Lock » convertie en un autre type va probablement utiliser un verrouillage inattendu basé sur un moniteur dans l’instruction SyncLock. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Une valeur de type « System.Threading.Lock » convertie en un autre type va probablement utiliser un verrouillage inattendu basé sur un moniteur dans l’instruction SyncLock. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf index b7a706ad51d69..545a70810c6fc 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + Un valore di tipo 'System.Threading.Lock' non è supportato in SyncLock. Prendi in considerazione di chiamare manualmente i metodi 'Enter' e 'Exit' in un blocco Try/Finally. @@ -616,12 +616,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Un valore di tipo 'System.Threading.Lock' convertito in un tipo diverso userà probabilmente un blocco basato su monitoraggio non intenzionale nell'istruzione SyncLock. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Un valore di tipo 'System.Threading.Lock' convertito in un tipo diverso userà probabilmente un blocco basato su monitoraggio non intenzionale nell'istruzione SyncLock. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf index 3db9ff53067e3..ee109b709581c 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + 型 'System.Threading.Lock' の値は SyncLock ではサポートされていません。代わりに Try/Finally ブロック内で 'Enter' メソッドと 'Exit' メソッドを手動で呼び出すことを検討してください。 @@ -617,12 +617,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + 型 'System.Threading.Lock' の値が別の型に変換されると、SyncLock ステートメントで意図しない可能性の高いモニターベースのロックが使用されます。 A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + 型 'System.Threading.Lock' の値が別の型に変換されると、SyncLock ステートメントで意図しない可能性の高いモニターベースのロックが使用されます。 diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf index 50b92bd5a2033..9e4c556724f61 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + SyncLock에서는 'System.Threading.Lock' 형식의 값이 지원되지 않습니다. 대신 Try/Finally 블록에서 'Enter' 및 'Exit' 메서드를 수동으로 호출하는 것이 좋습니다. @@ -615,12 +615,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + 다른 형식으로 변환된 'System.Threading.Lock' 형식의 값은 SyncLock 문에서 의도하지 않은 모니터 기반 잠금을 사용합니다. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + 다른 형식으로 변환된 'System.Threading.Lock' 형식의 값은 SyncLock 문에서 의도하지 않은 모니터 기반 잠금을 사용합니다. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf index 71119e9928aaa..51fcc464d55d1 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + Wartość typu „System.Threading.Lock” nie jest obsługiwana w funkcji SyncLock. Zamiast tego, rozważ ręczne wywołanie metod „Wprowadź” i „Wyjdź” w bloku Spróbuj/Zakończ. @@ -615,12 +615,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Wartość typu „System.Threading.Lock” przekonwertowana na inny typ użyje prawdopodobnie niezamierzonej blokady opartej na monitorze w instrukcji SyncLock. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Wartość typu „System.Threading.Lock” przekonwertowana na inny typ użyje prawdopodobnie niezamierzonej blokady opartej na monitorze w instrukcji SyncLock. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf index fdde610723f8e..68763831aa4b2 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + Um valor do tipo "System.Threading.Lock" não tem suporte no SyncLock. Em vez disso, pense em chamar manualmente os métodos "Enter" e "Exit" em um bloco Try/Finally. @@ -615,12 +615,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Um valor do tipo "System.Threading.Lock" convertido em um tipo diferente usará um bloqueio baseado em monitor provavelmente não intencional na instrução SyncLock. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Um valor do tipo "System.Threading.Lock" convertido em um tipo diferente usará um bloqueio baseado em monitor provavelmente não intencional na instrução SyncLock. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf index e8db68f9da02c..2fd0c0cae3ba5 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + Значение типа "System.Threading.Lock" не поддерживается в SyncLock. Попробуйте вручную вызвать методы "Enter" и "Exit" в блоке Try/Finally. @@ -615,12 +615,12 @@ optionstrict[+|-] Принудительное применени A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Значение типа "System.Threading.Lock", преобразованное в другой тип, будет использовать (скорее всего, непреднамеренную) блокировку на основе монитора в инструкции SyncLock. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Значение типа "System.Threading.Lock", преобразованное в другой тип, будет использовать (скорее всего, непреднамеренную) блокировку на основе монитора в инструкции SyncLock. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf index 15b539366c895..82b0482a6778a 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + SyncLock'ta 'System.Threading.Lock' türünde bir değer desteklenmiyor. Bunun yerine Try/Finally bloğunda 'Enter' ve 'Exit' yöntemlerini el ile çağırmayı düşünün. @@ -616,12 +616,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Farklı bir türe dönüştürülen 'System.Threading.Lock' türündeki bir değer, SyncLock deyiminde büyük olasılıkla istenmeyen izleyici tabanlı kilitlemeyi kullanır. A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + Farklı bir türe dönüştürülen 'System.Threading.Lock' türündeki bir değer, SyncLock deyiminde büyük olasılıkla istenmeyen izleyici tabanlı kilitlemeyi kullanır. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf index 35aa90cfe0f0b..604c6475d16fc 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + SyncLock 不支持类型为“System.Threading.Lock”的值。请考虑改为在 Try/Finally 块中手动调用“Enter”和“Exit”方法。 @@ -615,12 +615,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + 转换为不同类型的类型“System.Threading.Lock”的值将在 SyncLock 语句中使用可能意外的基于监视器的锁定。 A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + 转换为不同类型的类型“System.Threading.Lock”的值将在 SyncLock 语句中使用可能意外的基于监视器的锁定。 diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf index 2ea1daf390895..fd3f2dfc8883c 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf @@ -49,7 +49,7 @@ A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. - A value of type 'System.Threading.Lock' is not supported in SyncLock. Consider manually calling 'Enter' and 'Exit' methods in a Try/Finally block instead. + SyncLock 不支援型別 'System.Threading.Lock' 的值。請考慮改為在 Try/Finally 區塊中手動呼叫 'Enter' 和 'Exit' 方法。 @@ -616,12 +616,12 @@ A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + 型別 'System.Threading.Lock' 轉換為不同型別的值,在 SyncLock 陳述式中可能會使用非預期的監視器型鎖定。 A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. - A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in SyncLock statement. + 型別 'System.Threading.Lock' 轉換為不同型別的值,在 SyncLock 陳述式中可能會使用非預期的監視器型鎖定。 diff --git a/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingErrorTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingErrorTests.vb index 3528d48dc64b0..712f2eb4d7ab4 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingErrorTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingErrorTests.vb @@ -14072,6 +14072,52 @@ BC31440: 'AddressOf' cannot be applied to 'Private Sub Foo()' because 'Private S ) End Sub + + + Public Sub BC31440ERR_NoPartialMethodInAddressOf_02() + Dim source = +"Delegate Sub D() +Partial Class Program + Shared Sub Main() + M1(AddressOf M2(Of Integer)) + End Sub + Shared Sub M1(d As D) + End Sub + Private Shared Partial Sub M2(Of T)() + End Sub +End Class" + Dim comp = CreateCompilation(source) + comp.AssertTheseEmitDiagnostics( + + BC31440: 'AddressOf' cannot be applied to 'Private Shared Sub M2(Of Integer)()' because 'Private Shared Sub M2(Of Integer)()' is a partial method without an implementation. + M1(AddressOf M2(Of Integer)) + ~~~~~~~~~~~~~~ +) + End Sub + + + + Public Sub BC31440ERR_NoPartialMethodInAddressOf_03() + Dim source = +"Delegate Sub D() +Partial Class C(Of T) + Shared Sub M1() + M2(AddressOf C(Of Integer).M3) + End Sub + Shared Sub M2(d As D) + End Sub + Private Shared Partial Sub M3() + End Sub +End Class" + Dim comp = CreateCompilation(source) + comp.AssertTheseEmitDiagnostics( + + BC31440: 'AddressOf' cannot be applied to 'Private Shared Sub M3()' because 'Private Shared Sub M3()' is a partial method without an implementation. + M2(AddressOf C(Of Integer).M3) + ~~~~~~~~~~~~~~~~ +) + End Sub + Public Sub BC31500ERR_BadAttributeSharedProperty1() Dim compilation = CompilationUtils.CreateCompilationWithMscorlib40( diff --git a/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingObjectInitializerTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingObjectInitializerTests.vb index aa9e152b4d414..c008d8dd5f120 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingObjectInitializerTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingObjectInitializerTests.vb @@ -2073,6 +2073,52 @@ Console.writeline( cust2.e.ToString) CompileAndVerify(compilation) End Sub + + Public Sub RefReturningProperty() + Dim cSharpSource = ref _f; + } +}]]>.Value + Dim cSharpCompilation = CreateCSharpCompilation(cSharpSource).VerifyDiagnostics() + Dim cSharpRef = cSharpCompilation.EmitToPortableExecutableReference() + + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of ObjectCreationExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics, references:={cSharpRef}) + + CompileAndVerify(CreateCompilation(source, {cSharpRef}, TestOptions.ReleaseExe), expectedOutput:=).VerifyDiagnostics() + End Sub + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/RequiredMembersTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/RequiredMembersTests.vb index d71521c1d7937..f7abc9ccd3903 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/RequiredMembersTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/RequiredMembersTests.vb @@ -13,7 +13,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests Inherits BasicTestBase Private Function CreateCSharpCompilationWithRequiredMembers(source As String) As CSharpCompilation - Return CreateCSharpCompilation(source, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Return CreateCSharpCompilation(source, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) End Function @@ -424,14 +424,14 @@ End Module" Public Sub EnforcedRequiredMembers_ThroughRetargeting_NoneSet() Dim retargetedCode = GetCDefinition(hasSetsRequiredMembers:=False) - Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim originalBasic = CreateCompilation(" Public Class Base Public Property C As C End Class", {originalC.EmitToImageReference()}) - Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim comp = CreateCompilation(" Module M @@ -455,14 +455,14 @@ BC37321: Required member 'Public Overloads Property Prop As Integer' must be set Public Sub EnforcedRequiredMembers_ThroughRetargeting_AllSet( constructor As String) Dim retargetedCode = GetCDefinition(hasSetsRequiredMembers:=False) - Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim originalBasic = CreateCompilation(" Public Class Base Public Property C As C End Class", {originalC.EmitToImageReference()}) - Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim comp = CreateCompilation(" Module M @@ -480,14 +480,14 @@ End Module", {originalBasic.ToMetadataReference(), retargetedC.EmitToImageRefere Dim retargetedCode = GetCDefinition(hasSetsRequiredMembers:=True) - Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim originalBasic = CreateCompilation(" Public Class Base Public Property C As C End Class", {originalC.EmitToImageReference()}) - Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim comp = CreateCompilation(" Module M @@ -504,14 +504,14 @@ End Module", {originalBasic.ToMetadataReference(), retargetedC.EmitToImageRefere Dim codeWithRequired = GetCDefinition(hasSetsRequiredMembers:=False) Dim codeWithoutRequired = codeWithRequired.Replace("required ", "") - Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), codeWithoutRequired, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), codeWithoutRequired, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim originalBasic = CreateCompilation(" Public Class Derived Inherits C End Class", {originalC.EmitToImageReference()}) - Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), codeWithRequired, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), codeWithRequired, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim comp = CreateCompilation(" Module M @@ -543,7 +543,7 @@ End Class", targetFramework:=TargetFramework.Net70) public class Derived : Base { public required int Prop { get; set; } -}", referencedAssemblies:=DirectCast(Basic.Reference.Assemblies.Net70.All, IEnumerable(Of MetadataReference)).Append(originalVbComp.EmitToImageReference())) +}", referencedAssemblies:=DirectCast(Basic.Reference.Assemblies.Net70.References.All, IEnumerable(Of MetadataReference)).Append(originalVbComp.EmitToImageReference())) Dim comp = CreateCompilation($" Module M @@ -1952,7 +1952,7 @@ namespace System } } } -", referencedAssemblies:=Basic.Reference.Assemblies.Net461.All) +", referencedAssemblies:=Basic.Reference.Assemblies.Net461.References.All) Dim csharpCompReference As MetadataReference = csharpComp.EmitToImageReference() ' Using Net461 to get a framework without ValueTuple @@ -2143,7 +2143,7 @@ namespace System } } } -", referencedAssemblies:=Basic.Reference.Assemblies.Net461.All) +", referencedAssemblies:=Basic.Reference.Assemblies.Net461.References.All) ' Using Net461 to get a framework without ValueTuple diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CorLibrary/CorTypes.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CorLibrary/CorTypes.vb index 5ad9b8231f66e..cfdb9d5f2cbe3 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CorLibrary/CorTypes.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CorLibrary/CorTypes.vb @@ -4,7 +4,9 @@ Imports Basic.Reference.Assemblies Imports CompilationCreationTestHelpers +Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.CodeAnalysis.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Symbols.CorLibrary @@ -262,6 +264,711 @@ End Namespace Assert.Same(c1TestClassT, c4.GetTypeByMetadataName("System.TestClass`1")) End Sub + + Public Sub System_Type__WellKnownVsSpecial_01() + Dim source = + + + + + Dim comp = CreateCompilation(source, options:=TestOptions.DebugExe) + comp.MakeMemberMissing(WellKnownMember.System_Type__GetTypeFromHandle) + + Assert.False(comp.GetSpecialType(InternalSpecialType.System_Type).IsErrorType()) + + Dim tree = comp.SyntaxTrees.Single() + Dim node = tree.GetRoot().DescendantNodes().OfType(Of GetTypeExpressionSyntax)().Single() + Dim model = comp.GetSemanticModel(tree) + + Assert.Equal(InternalSpecialType.System_Type, DirectCast(model.GetTypeInfo(node).Type, TypeSymbol).ExtendedSpecialType) + + CompileAndVerify(comp, expectedOutput:="Program") + + comp = CreateCompilation(source, options:=TestOptions.DebugExe) + comp.MakeMemberMissing(SpecialMember.System_Type__GetTypeFromHandle) + comp.AssertTheseEmitDiagnostics( + +BC35000: Requested operation is not available because the runtime library function 'System.Type.GetTypeFromHandle' is not defined. + Dim x = GetType(Program) + ~~~~~~~~~~~~~~~~ + + ) + End Sub + + + Public Sub System_Type__WellKnownVsSpecial_02() + Dim corLib_v1 = " +namespace System +{ + public class Object + {} + + public class Void + {} + + public class ValueType + {} + + public struct RuntimeTypeHandle + {} + + public struct Int32 + {} + + public struct Boolean + {} + + public class Attribute + {} + + public class Enum + {} + + public enum AttributeTargets + { + } + + public class AttributeUsageAttribute + { + public AttributeUsageAttribute(AttributeTargets validOn){} + public bool AllowMultiple => false; + public bool Inherited => false; + } +} +" + Dim corLib_v1_Comp = CreateCSharpCompilation(corLib_v1, referencedAssemblies:={}, assemblyName:="corLib") + + Dim typeLib_v1 = " +namespace System +{ + public class Type + { + public static Type GetTypeFromHandle(RuntimeTypeHandle handle) => null; + } +} +" + Dim corLib_v1_Ref = corLib_v1_Comp.EmitToImageReference() + + Dim typeLib_v1_Comp = CreateCSharpCompilation(typeLib_v1, referencedAssemblies:={corLib_v1_Ref}, assemblyName:="typeLib") + + Dim source1 = + + + + Dim typeLib_v1_Ref = typeLib_v1_Comp.EmitToImageReference() + + Dim comp1 = CreateEmptyCompilation(source1, references:={corLib_v1_Ref, typeLib_v1_Ref}) + + Assert.True(comp1.GetSpecialType(InternalSpecialType.System_Type).IsErrorType()) + comp1.MakeMemberMissing(SpecialMember.System_Type__GetTypeFromHandle) + + Dim tree = comp1.SyntaxTrees.Single() + Dim node = tree.GetRoot().DescendantNodes().OfType(Of GetTypeExpressionSyntax)().Single() + Dim model = comp1.GetSemanticModel(tree) + + Assert.Equal(CType(0, ExtendedSpecialType), DirectCast(model.GetTypeInfo(node).Type, TypeSymbol).ExtendedSpecialType) + + Dim comp1Ref = comp1.EmitToImageReference() + + Dim corLib_v2 = " +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Object))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(void))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ValueType))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeTypeHandle))] +" + Dim corLib_v2_Comp = CreateCSharpCompilation( + corLib_v2, + referencedAssemblies:=TargetFrameworkUtil.GetReferences(TargetFramework.Standard), + assemblyName:="corLib") + + Dim typeLib_v2 = " +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Type))] +" + + Dim typeLib_v2_Comp = CreateCSharpCompilation( + typeLib_v2, + referencedAssemblies:=TargetFrameworkUtil.GetReferences(TargetFramework.Standard), + assemblyName:="typeLib") + + Dim source2 = + + + + + Dim comp = CreateCompilation(source2, + references:={corLib_v2_Comp.EmitToImageReference(), typeLib_v2_Comp.EmitToImageReference(), comp1Ref}, + options:=TestOptions.DebugExe) + CompileAndVerify(comp, expectedOutput:="Test") + + comp1 = CreateEmptyCompilation(source1, references:={corLib_v1_Ref, typeLib_v1_Ref}) + + comp1.MakeMemberMissing(WellKnownMember.System_Type__GetTypeFromHandle) + comp1.AssertTheseEmitDiagnostics( + +BC35000: Requested operation is not available because the runtime library function 'System.Type.GetTypeFromHandle' is not defined. + Return GetType(Test) + ~~~~~~~~~~~~~ + + ) + End Sub + + + Public Sub CreateDelegate__MethodInfoVsDelegate_01() + Dim source = + + + + + Dim comp = CreateCompilation(source, targetFramework:=TargetFramework.Mscorlib40AndSystemCore, options:=TestOptions.DebugExe) + comp.MakeMemberMissing(WellKnownMember.System_Reflection_MethodInfo__CreateDelegate) + comp.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle2) + comp.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle) + comp.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2) + + CompileAndVerify(comp, expectedOutput:="() => Convert(CreateDelegate(System.Action, null, Void M1(), False)" + + If(ExecutionConditionUtil.IsMonoOrCoreClr, ", Action", "") + + ")") + + comp = CreateCompilation(source, targetFramework:=TargetFramework.Mscorlib40AndSystemCore, options:=TestOptions.DebugExe) + comp.MakeMemberMissing(SpecialMember.System_Delegate__CreateDelegate4) + comp.AssertTheseEmitDiagnostics( + +BC35000: Requested operation is not available because the runtime library function 'System.Delegate.CreateDelegate' is not defined. + Dim x As System.Linq.Expressions.Expression(Of System.Func(Of System.Action))= Function() AddressOf C1.M1 + ~~~~~~~~~~~~~~~ + + ) + + comp = CreateCompilation(source, targetFramework:=TargetFramework.Mscorlib40AndSystemCore, options:=TestOptions.DebugExe) + comp.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle) + comp.AssertTheseEmitDiagnostics( + +BC35000: Requested operation is not available because the runtime library function 'System.Reflection.MethodBase.GetMethodFromHandle' is not defined. + Dim x As System.Linq.Expressions.Expression(Of System.Func(Of System.Action))= Function() AddressOf C1.M1 + ~~~~~~~~~~~~~~~ +BC35000: Requested operation is not available because the runtime library function 'System.Reflection.MethodBase.GetMethodFromHandle' is not defined. + Dim x As System.Linq.Expressions.Expression(Of System.Func(Of System.Action))= Function() AddressOf C1.M1 + ~~~~~~~~~~~~~~~ + + ) + End Sub + + + Public Sub CreateDelegate__MethodInfoVsDelegate_02() + Dim source = + + + + + Dim comp = CreateCompilation(source, options:=TestOptions.DebugExe) + comp.MakeMemberMissing(SpecialMember.System_Delegate__CreateDelegate) + comp.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle) + comp.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2) + + CompileAndVerify( + comp, expectedOutput:="() => Convert(Void M1().CreateDelegate(System.Action, null)" + + If(ExecutionConditionUtil.IsMonoOrCoreClr, ", Action", "") + + ")") + + comp = CreateCompilation(source, options:=TestOptions.DebugExe) + comp.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle2) + comp.AssertTheseEmitDiagnostics( + +BC35000: Requested operation is not available because the runtime library function 'System.Reflection.MethodBase.GetMethodFromHandle' is not defined. + Dim x As System.Linq.Expressions.Expression(Of System.Func(Of System.Action))= Function() AddressOf C1(Of Integer).M1 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ) + End Sub + + + Public Sub GetMethodFromHandle_WellKnown_01() + Dim corLib_v1 = " +namespace System +{ + public class Object + {} + + public class Void + {} + + public class ValueType + {} + + public struct RuntimeTypeHandle + {} + + public struct RuntimeMethodHandle + {} + + public struct Int32 + {} + + public abstract class Delegate + {} + + public abstract class MulticastDelegate : Delegate + {} + + public delegate void Action(); + + public delegate TResult Func(); + + public struct Nullable + {} + + public struct IntPtr + {} + + public struct Boolean + {} + + public class Attribute + {} + + public class Enum + {} + + public enum AttributeTargets + { + } + + public class AttributeUsageAttribute + { + public AttributeUsageAttribute(AttributeTargets validOn){} + public bool AllowMultiple => false; + public bool Inherited => false; + } +} + +namespace System.Collections.Generic +{ + public interface IEnumerable + {} +} +" + Dim corLib_v1_Comp = CreateCSharpCompilation(corLib_v1, referencedAssemblies:={}, assemblyName:="corLib") + + Dim typeLib_v1 = " +namespace System +{ + public class Type + { + public static Type GetTypeFromHandle(RuntimeTypeHandle handle) => null; + } +} +namespace System.Reflection +{ + public abstract partial class MethodBase + { + public static MethodBase GetMethodFromHandle(RuntimeMethodHandle handle) => null; + } + + public abstract partial class MethodInfo : MethodBase + { + public virtual Delegate CreateDelegate(Type delegateType) => null; + public virtual Delegate CreateDelegate(Type delegateType, object target) => null; + } +} + +namespace System.Linq.Expressions +{ + public abstract class Expression + { + public static ConstantExpression Constant (object value) => null; + public static ConstantExpression Constant (object value, Type type) => null; + + public static MethodCallExpression Call (Expression instance, System.Reflection.MethodInfo method, Expression[] arguments) => null; + + public static UnaryExpression Convert (Expression expression, Type type) => null; + public static UnaryExpression Convert (Expression expression, Type type, System.Reflection.MethodInfo method) => null; + + public static Expression Lambda (Expression body, ParameterExpression[] parameters) => null; + } + + public abstract class LambdaExpression : Expression + {} + + public abstract class Expression : LambdaExpression + {} + + public class ConstantExpression : Expression + {} + + public class ParameterExpression : Expression + {} + + public class MethodCallExpression : Expression + {} + + public sealed class UnaryExpression : Expression + {} +} +" + Dim corLib_v1_Ref = corLib_v1_Comp.EmitToImageReference() + + Dim typeLib_v1_Comp = CreateCSharpCompilation(typeLib_v1, referencedAssemblies:={corLib_v1_Ref}, assemblyName:="typeLib") + + typeLib_v1_Comp.VerifyDiagnostics() + + Dim source1 = + + + + Dim typeLib_v1_Ref = typeLib_v1_Comp.EmitToImageReference() + Dim comp1 = CreateEmptyCompilation(source1, references:={corLib_v1_Ref, typeLib_v1_Ref}) + + comp1.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle) + comp1.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle2) + comp1.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2) + + Dim comp1Ref = comp1.EmitToImageReference() + + Dim corLib_v2 = " +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Object))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(void))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ValueType))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeTypeHandle))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeMethodHandle))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Int32))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Action))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Func<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Nullable<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.IEnumerable<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Delegate))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.MulticastDelegate))] +" + Dim corLib_v2_Comp = CreateCSharpCompilation( + corLib_v2, + referencedAssemblies:=TargetFrameworkUtil.GetReferences(TargetFramework.Standard), + assemblyName:="corLib") + + Dim typeLib_v2 = " +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Type))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.MethodBase))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.MethodInfo))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.Expression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.LambdaExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.Expression<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.ConstantExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.ParameterExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.MethodCallExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.UnaryExpression))] +" + + Dim typeLib_v2_Comp = CreateCSharpCompilation( + typeLib_v2, + referencedAssemblies:=TargetFrameworkUtil.GetReferences(TargetFramework.Standard), + assemblyName:="typeLib") + + Dim source2 = + + + + + Dim comp = CreateCompilation(source2, references:={corLib_v2_Comp.EmitToImageReference(), typeLib_v2_Comp.EmitToImageReference(), comp1Ref}, options:=TestOptions.DebugExe) + + CompileAndVerify(comp, expectedOutput:="() => Convert(Void M1().CreateDelegate(System.Action, null)" + + If(ExecutionConditionUtil.IsMonoOrCoreClr, ", Action", "") + + ")") + + comp1 = CreateEmptyCompilation( + source1, references:={corLib_v1_Ref, typeLib_v1_Ref}) + + comp1.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle) + comp1.AssertTheseEmitDiagnostics( + +BC35000: Requested operation is not available because the runtime library function 'System.Reflection.MethodBase.GetMethodFromHandle' is not defined. + return Function() AddressOf C1.M1 + ~~~~~~~~~~~~~~~ +BC35000: Requested operation is not available because the runtime library function 'System.Reflection.MethodBase.GetMethodFromHandle' is not defined. + return Function() AddressOf C1.M1 + ~~~~~~~~~~~~~~~ + + ) + End Sub + + + Public Sub GetMethodFromHandle_WellKnown_02() + Dim corLib_v1 = " +namespace System +{ + public class Object + {} + + public class Void + {} + + public class ValueType + {} + + public struct RuntimeTypeHandle + {} + + public struct RuntimeMethodHandle + {} + + public struct Int32 + {} + + public abstract class Delegate + {} + + public abstract class MulticastDelegate : Delegate + {} + + public delegate void Action(); + + public delegate TResult Func(); + + public struct Nullable + {} + + public struct IntPtr + {} + + public struct Boolean + {} + + public class Attribute + {} + + public class Enum + {} + + public enum AttributeTargets + { + } + + public class AttributeUsageAttribute + { + public AttributeUsageAttribute(AttributeTargets validOn){} + public bool AllowMultiple => false; + public bool Inherited => false; + } +} + +namespace System.Collections.Generic +{ + public interface IEnumerable + {} +} +" + Dim corLib_v1_Comp = CreateCSharpCompilation(corLib_v1, referencedAssemblies:={}, assemblyName:="corLib") + + Dim typeLib_v1 = " +namespace System +{ + public class Type + { + public static Type GetTypeFromHandle(RuntimeTypeHandle handle) => null; + } +} +namespace System.Reflection +{ + public abstract partial class MethodBase + { + public static MethodBase GetMethodFromHandle(RuntimeMethodHandle handle) => null; + public static MethodBase GetMethodFromHandle(RuntimeMethodHandle handle, RuntimeTypeHandle declaringType) => null; + } + + public abstract partial class MethodInfo : MethodBase + { + public virtual Delegate CreateDelegate(Type delegateType) => null; + public virtual Delegate CreateDelegate(Type delegateType, object target) => null; + } +} + +namespace System.Linq.Expressions +{ + public abstract class Expression + { + public static ConstantExpression Constant (object value) => null; + public static ConstantExpression Constant (object value, Type type) => null; + + public static MethodCallExpression Call (Expression instance, System.Reflection.MethodInfo method, Expression[] arguments) => null; + + public static UnaryExpression Convert (Expression expression, Type type) => null; + public static UnaryExpression Convert (Expression expression, Type type, System.Reflection.MethodInfo method) => null; + + public static Expression Lambda (Expression body, ParameterExpression[] parameters) => null; + } + + public abstract class LambdaExpression : Expression + {} + + public abstract class Expression : LambdaExpression + {} + + public class ConstantExpression : Expression + {} + + public class ParameterExpression : Expression + {} + + public class MethodCallExpression : Expression + {} + + public sealed class UnaryExpression : Expression + {} +} +" + Dim corLib_v1_Ref = corLib_v1_Comp.EmitToImageReference() + + Dim typeLib_v1_Comp = CreateCSharpCompilation(typeLib_v1, referencedAssemblies:={corLib_v1_Ref}, assemblyName:="typeLib") + + typeLib_v1_Comp.VerifyDiagnostics() + + Dim source1 = + + + + Dim typeLib_v1_Ref = typeLib_v1_Comp.EmitToImageReference() + Dim comp1 = CreateEmptyCompilation(source1, references:={corLib_v1_Ref, typeLib_v1_Ref}) + + comp1.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle) + comp1.MakeMemberMissing(SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle2) + + Dim comp1Ref = comp1.EmitToImageReference() + + Dim corLib_v2 = " +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Object))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(void))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ValueType))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeTypeHandle))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.RuntimeMethodHandle))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Int32))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Action))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Func<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Nullable<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.IEnumerable<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Delegate))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.MulticastDelegate))] +" + Dim corLib_v2_Comp = CreateCSharpCompilation( + corLib_v2, + referencedAssemblies:=TargetFrameworkUtil.GetReferences(TargetFramework.Standard), + assemblyName:="corLib") + + Dim typeLib_v2 = " +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Type))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.MethodBase))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.MethodInfo))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.Expression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.LambdaExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.Expression<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.ConstantExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.ParameterExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.MethodCallExpression))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Linq.Expressions.UnaryExpression))] +" + + Dim typeLib_v2_Comp = CreateCSharpCompilation( + typeLib_v2, + referencedAssemblies:=TargetFrameworkUtil.GetReferences(TargetFramework.Standard), + assemblyName:="typeLib") + + Dim source2 = + + + + + Dim comp = CreateCompilation(source2, references:={corLib_v2_Comp.EmitToImageReference(), typeLib_v2_Comp.EmitToImageReference(), comp1Ref}, options:=TestOptions.DebugExe) + + CompileAndVerify(comp, expectedOutput:="() => Convert(Void M1().CreateDelegate(System.Action, null)" + + If(ExecutionConditionUtil.IsMonoOrCoreClr, ", Action", "") + + ")") + + comp1 = CreateEmptyCompilation( + source1, references:={corLib_v1_Ref, typeLib_v1_Ref}) + + comp1.MakeMemberMissing(WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2) + comp1.AssertTheseEmitDiagnostics( + +BC35000: Requested operation is not available because the runtime library function 'System.Reflection.MethodBase.GetMethodFromHandle' is not defined. + return Function() AddressOf C1(Of Integer).M1 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ) + End Sub + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb b/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb index 3b82a4a5be9fd..0c1d011590a7e 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb @@ -3,6 +3,7 @@ ' See the LICENSE file in the project root for more information. Imports System.IO +Imports System.Reflection Imports System.Text Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.VisualBasic @@ -1262,4 +1263,15 @@ End Module Assert.Equal(isReserved, SyntaxFacts.IsReservedTupleElementName(elementName)) End Sub + + Public Sub TestAllKindsReturnedFromGetKindsMethodsExist() + For Each method In GetType(SyntaxFacts).GetMethods(BindingFlags.Public Or BindingFlags.Static) + If method.ReturnType = GetType(IEnumerable(Of SyntaxKind)) AndAlso method.GetParameters().Length = 0 Then + For Each kind As SyntaxKind In DirectCast(method.Invoke(Nothing, Nothing), IEnumerable(Of SyntaxKind)) + Assert.True([Enum].IsDefined(GetType(SyntaxKind), kind), $"Nonexistent kind '{kind}' returned from method '{method.Name}'") + Next + End If + Next + End Sub + End Class diff --git a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs index b99e4e99373cc..c28d85c2ae2a8 100644 --- a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs +++ b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs @@ -349,7 +349,7 @@ void ICollection.CopyTo(Array array, int index) if (array is null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (index < 0) - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); if (array.Length < index + Count) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); diff --git a/src/Dependencies/Collections/ImmutableSegmentedList`1+ValueBuilder.cs b/src/Dependencies/Collections/ImmutableSegmentedList`1+ValueBuilder.cs index 126b8ccd6065d..456d50419de01 100644 --- a/src/Dependencies/Collections/ImmutableSegmentedList`1+ValueBuilder.cs +++ b/src/Dependencies/Collections/ImmutableSegmentedList`1+ValueBuilder.cs @@ -61,7 +61,7 @@ public readonly ref readonly T ItemRef(int index) // Following trick can reduce the range check by one if ((uint)index >= (uint)ReadOnlyList.Count) { - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); } return ref ReadOnlyList._items[index]; @@ -242,7 +242,7 @@ public readonly int LastIndexOf(T item, int startIndex, int count) public readonly int LastIndexOf(T item, int startIndex, int count, IEqualityComparer? equalityComparer) { if (startIndex < 0) - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); if (count < 0 || count > Count) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count); if (startIndex - count + 1 < 0) diff --git a/src/Dependencies/Collections/ImmutableSegmentedList`1.cs b/src/Dependencies/Collections/ImmutableSegmentedList`1.cs index 46ab790309176..b8eb5c0f54524 100644 --- a/src/Dependencies/Collections/ImmutableSegmentedList`1.cs +++ b/src/Dependencies/Collections/ImmutableSegmentedList`1.cs @@ -129,7 +129,7 @@ public ref readonly T ItemRef(int index) // Following trick can reduce the number of range comparison operations by one if (unchecked((uint)index) >= (uint)self.Count) { - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); } return ref self._list._items[index]; @@ -339,7 +339,7 @@ public int LastIndexOf(T item, int index, int count, IEqualityComparer? equal var self = this; if (index < 0) - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); if (count < 0 || count > self.Count) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count); if (index - count + 1 < 0) diff --git a/src/Dependencies/Collections/Internal/ArraySortHelper.cs b/src/Dependencies/Collections/Internal/ArraySortHelper.cs index 6387b11161978..7c12f6f76a234 100644 --- a/src/Dependencies/Collections/Internal/ArraySortHelper.cs +++ b/src/Dependencies/Collections/Internal/ArraySortHelper.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ArraySortHelper.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ArraySortHelper.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -19,6 +19,8 @@ using System.Runtime.InteropServices; #endif +#pragma warning disable CA1822 + namespace Microsoft.CodeAnalysis.Collections.Internal { #region ArraySortHelper for single arrays @@ -135,6 +137,9 @@ internal static void IntrospectiveSort(SegmentedArraySegment keys, Comparison } } + // IntroSort is recursive; block it from being inlined into itself as + // this is currenly not profitable. + [MethodImpl(MethodImplOptions.NoInlining)] private static void IntroSort(SegmentedArraySegment keys, int depthLimit, Comparison comparer) { Debug.Assert(keys.Length > 0); @@ -233,39 +238,37 @@ private static void HeapSort(SegmentedArraySegment keys, Comparison compar int n = keys.Length; for (int i = n >> 1; i >= 1; i--) { - DownHeap(keys, i, n, 0, comparer!); + DownHeap(keys, i, n, comparer!); } for (int i = n; i > 1; i--) { Swap(keys, 0, i - 1); - DownHeap(keys, 1, i - 1, 0, comparer!); + DownHeap(keys, 1, i - 1, comparer!); } } - private static void DownHeap(SegmentedArraySegment keys, int i, int n, int lo, Comparison comparer) + private static void DownHeap(SegmentedArraySegment keys, int i, int n, Comparison comparer) { Debug.Assert(comparer != null); - Debug.Assert(lo >= 0); - Debug.Assert(lo < keys.Length); - T d = keys[lo + i - 1]; + T d = keys[i - 1]; while (i <= n >> 1) { int child = 2 * i; - if (child < n && comparer!(keys[lo + child - 1], keys[lo + child]) < 0) + if (child < n && comparer!(keys[child - 1], keys[child]) < 0) { child++; } - if (!(comparer!(d, keys[lo + child - 1]) < 0)) + if (!(comparer!(d, keys[child - 1]) < 0)) break; - keys[lo + i - 1] = keys[lo + child - 1]; + keys[i - 1] = keys[child - 1]; i = child; } - keys[lo + i - 1] = d; + keys[i - 1] = d; } private static void InsertionSort(SegmentedArraySegment keys, Comparison comparer) @@ -414,6 +417,9 @@ private static void Swap(ref T i, ref T j) j = t; } + // IntroSort is recursive; block it from being inlined into itself as + // this is currenly not profitable. + [MethodImpl(MethodImplOptions.NoInlining)] private static void IntroSort(SegmentedArraySegment keys, int depthLimit) { Debug.Assert(keys.Length > 0); @@ -531,38 +537,35 @@ private static void HeapSort(SegmentedArraySegment keys) int n = keys.Length; for (int i = n >> 1; i >= 1; i--) { - DownHeap(keys, i, n, 0); + DownHeap(keys, i, n); } for (int i = n; i > 1; i--) { Swap(ref keys[0], ref keys[i - 1]); - DownHeap(keys, 1, i - 1, 0); + DownHeap(keys, 1, i - 1); } } - private static void DownHeap(SegmentedArraySegment keys, int i, int n, int lo) + private static void DownHeap(SegmentedArraySegment keys, int i, int n) { - Debug.Assert(lo >= 0); - Debug.Assert(lo < keys.Length); - - T d = keys[lo + i - 1]; + T d = keys[i - 1]; while (i <= n >> 1) { int child = 2 * i; - if (child < n && (keys[lo + child - 1] == null || LessThan(ref keys[lo + child - 1], ref keys[lo + child]))) + if (child < n && (keys[child - 1] == null || LessThan(ref keys[child - 1], ref keys[child]))) { child++; } - if (keys[lo + child - 1] == null || !LessThan(ref d, ref keys[lo + child - 1])) + if (keys[child - 1] == null || !LessThan(ref d, ref keys[child - 1])) break; - keys[lo + i - 1] = keys[lo + child - 1]; + keys[i - 1] = keys[child - 1]; i = child; } - keys[lo + i - 1] = d; + keys[i - 1] = d; } private static void InsertionSort(SegmentedArraySegment keys) @@ -588,39 +591,38 @@ private static void InsertionSort(SegmentedArraySegment keys) // - The floating-point comparisons here assume no NaNs, which is valid only because the sorting routines // themselves special-case NaN with a pre-pass that ensures none are present in the values being sorted // by moving them all to the front first and then sorting the rest. - // - The `? true : false` is to work-around poor codegen: https://github.com/dotnet/runtime/issues/37904#issuecomment-644180265. // - These are duplicated here rather than being on a helper type due to current limitations around generic inlining. [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call private static bool LessThan(ref T left, ref T right) { if (typeof(T) == typeof(byte)) - return (byte)(object)left < (byte)(object)right ? true : false; + return (byte)(object)left < (byte)(object)right; if (typeof(T) == typeof(sbyte)) - return (sbyte)(object)left < (sbyte)(object)right ? true : false; + return (sbyte)(object)left < (sbyte)(object)right; if (typeof(T) == typeof(ushort)) - return (ushort)(object)left < (ushort)(object)right ? true : false; + return (ushort)(object)left < (ushort)(object)right; if (typeof(T) == typeof(short)) - return (short)(object)left < (short)(object)right ? true : false; + return (short)(object)left < (short)(object)right; if (typeof(T) == typeof(uint)) - return (uint)(object)left < (uint)(object)right ? true : false; + return (uint)(object)left < (uint)(object)right; if (typeof(T) == typeof(int)) - return (int)(object)left < (int)(object)right ? true : false; + return (int)(object)left < (int)(object)right; if (typeof(T) == typeof(ulong)) - return (ulong)(object)left < (ulong)(object)right ? true : false; + return (ulong)(object)left < (ulong)(object)right; if (typeof(T) == typeof(long)) - return (long)(object)left < (long)(object)right ? true : false; + return (long)(object)left < (long)(object)right; if (typeof(T) == typeof(UIntPtr)) - return (nuint)(object)left < (nuint)(object)right ? true : false; + return (nuint)(object)left < (nuint)(object)right; if (typeof(T) == typeof(IntPtr)) - return (nint)(object)left < (nint)(object)right ? true : false; + return (nint)(object)left < (nint)(object)right; if (typeof(T) == typeof(float)) - return (float)(object)left < (float)(object)right ? true : false; + return (float)(object)left < (float)(object)right; if (typeof(T) == typeof(double)) - return (double)(object)left < (double)(object)right ? true : false; + return (double)(object)left < (double)(object)right; #if NET if (typeof(T) == typeof(Half)) - return (Half)(object)left < (Half)(object)right ? true : false; + return (Half)(object)left < (Half)(object)right; #endif return left.CompareTo(right) < 0 ? true : false; } @@ -629,32 +631,32 @@ private static bool LessThan(ref T left, ref T right) private static bool GreaterThan(ref T left, ref T right) { if (typeof(T) == typeof(byte)) - return (byte)(object)left > (byte)(object)right ? true : false; + return (byte)(object)left > (byte)(object)right; if (typeof(T) == typeof(sbyte)) - return (sbyte)(object)left > (sbyte)(object)right ? true : false; + return (sbyte)(object)left > (sbyte)(object)right; if (typeof(T) == typeof(ushort)) - return (ushort)(object)left > (ushort)(object)right ? true : false; + return (ushort)(object)left > (ushort)(object)right; if (typeof(T) == typeof(short)) - return (short)(object)left > (short)(object)right ? true : false; + return (short)(object)left > (short)(object)right; if (typeof(T) == typeof(uint)) - return (uint)(object)left > (uint)(object)right ? true : false; + return (uint)(object)left > (uint)(object)right; if (typeof(T) == typeof(int)) - return (int)(object)left > (int)(object)right ? true : false; + return (int)(object)left > (int)(object)right; if (typeof(T) == typeof(ulong)) - return (ulong)(object)left > (ulong)(object)right ? true : false; + return (ulong)(object)left > (ulong)(object)right; if (typeof(T) == typeof(long)) - return (long)(object)left > (long)(object)right ? true : false; + return (long)(object)left > (long)(object)right; if (typeof(T) == typeof(UIntPtr)) - return (nuint)(object)left > (nuint)(object)right ? true : false; + return (nuint)(object)left > (nuint)(object)right; if (typeof(T) == typeof(IntPtr)) - return (nint)(object)left > (nint)(object)right ? true : false; + return (nint)(object)left > (nint)(object)right; if (typeof(T) == typeof(float)) - return (float)(object)left > (float)(object)right ? true : false; + return (float)(object)left > (float)(object)right; if (typeof(T) == typeof(double)) - return (double)(object)left > (double)(object)right ? true : false; + return (double)(object)left > (double)(object)right; #if NET if (typeof(T) == typeof(Half)) - return (Half)(object)left > (Half)(object)right ? true : false; + return (Half)(object)left > (Half)(object)right; #endif return left.CompareTo(right) > 0 ? true : false; } @@ -827,43 +829,41 @@ private static void HeapSort(SegmentedArraySegment keys, Span valu int n = keys.Length; for (int i = n >> 1; i >= 1; i--) { - DownHeap(keys, values, i, n, 0, comparer!); + DownHeap(keys, values, i, n, comparer!); } for (int i = n; i > 1; i--) { Swap(keys, values, 0, i - 1); - DownHeap(keys, values, 1, i - 1, 0, comparer!); + DownHeap(keys, values, 1, i - 1, comparer!); } } - private static void DownHeap(SegmentedArraySegment keys, Span values, int i, int n, int lo, IComparer comparer) + private static void DownHeap(SegmentedArraySegment keys, Span values, int i, int n, IComparer comparer) { Debug.Assert(comparer != null); - Debug.Assert(lo >= 0); - Debug.Assert(lo < keys.Length); - TKey d = keys[lo + i - 1]; - TValue dValue = values[lo + i - 1]; + TKey d = keys[i - 1]; + TValue dValue = values[i - 1]; while (i <= n >> 1) { int child = 2 * i; - if (child < n && comparer!.Compare(keys[lo + child - 1], keys[lo + child]) < 0) + if (child < n && comparer!.Compare(keys[child - 1], keys[child]) < 0) { child++; } - if (!(comparer!.Compare(d, keys[lo + child - 1]) < 0)) + if (!(comparer!.Compare(d, keys[child - 1]) < 0)) break; - keys[lo + i - 1] = keys[lo + child - 1]; - values[lo + i - 1] = values[lo + child - 1]; + keys[i - 1] = keys[child - 1]; + values[i - 1] = values[child - 1]; i = child; } - keys[lo + i - 1] = d; - values[lo + i - 1] = dValue; + keys[i - 1] = d; + values[i - 1] = dValue; } private static void InsertionSort(SegmentedArraySegment keys, Span values, IComparer comparer) @@ -1081,42 +1081,39 @@ private static void HeapSort(SegmentedArraySegment keys, Span valu int n = keys.Length; for (int i = n >> 1; i >= 1; i--) { - DownHeap(keys, values, i, n, 0); + DownHeap(keys, values, i, n); } for (int i = n; i > 1; i--) { Swap(keys, values, 0, i - 1); - DownHeap(keys, values, 1, i - 1, 0); + DownHeap(keys, values, 1, i - 1); } } - private static void DownHeap(SegmentedArraySegment keys, Span values, int i, int n, int lo) + private static void DownHeap(SegmentedArraySegment keys, Span values, int i, int n) { - Debug.Assert(lo >= 0); - Debug.Assert(lo < keys.Length); - - TKey d = keys[lo + i - 1]; - TValue dValue = values[lo + i - 1]; + TKey d = keys[i - 1]; + TValue dValue = values[i - 1]; while (i <= n >> 1) { int child = 2 * i; - if (child < n && (keys[lo + child - 1] == null || LessThan(ref keys[lo + child - 1], ref keys[lo + child]))) + if (child < n && (keys[child - 1] == null || LessThan(ref keys[child - 1], ref keys[child]))) { child++; } - if (keys[lo + child - 1] == null || !LessThan(ref d, ref keys[lo + child - 1])) + if (keys[child - 1] == null || !LessThan(ref d, ref keys[child - 1])) break; - keys[lo + i - 1] = keys[lo + child - 1]; - values[lo + i - 1] = values[lo + child - 1]; + keys[i - 1] = keys[child - 1]; + values[i - 1] = values[child - 1]; i = child; } - keys[lo + i - 1] = d; - values[lo + i - 1] = dValue; + keys[i - 1] = d; + values[i - 1] = dValue; } private static void InsertionSort(SegmentedArraySegment keys, Span values) @@ -1145,39 +1142,38 @@ private static void InsertionSort(SegmentedArraySegment keys, Span // - The floating-point comparisons here assume no NaNs, which is valid only because the sorting routines // themselves special-case NaN with a pre-pass that ensures none are present in the values being sorted // by moving them all to the front first and then sorting the rest. - // - The `? true : false` is to work-around poor codegen: https://github.com/dotnet/runtime/issues/37904#issuecomment-644180265. // - These are duplicated here rather than being on a helper type due to current limitations around generic inlining. [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call private static bool LessThan(ref TKey left, ref TKey right) { if (typeof(TKey) == typeof(byte)) - return (byte)(object)left < (byte)(object)right ? true : false; + return (byte)(object)left < (byte)(object)right; if (typeof(TKey) == typeof(sbyte)) - return (sbyte)(object)left < (sbyte)(object)right ? true : false; + return (sbyte)(object)left < (sbyte)(object)right; if (typeof(TKey) == typeof(ushort)) - return (ushort)(object)left < (ushort)(object)right ? true : false; + return (ushort)(object)left < (ushort)(object)right; if (typeof(TKey) == typeof(short)) - return (short)(object)left < (short)(object)right ? true : false; + return (short)(object)left < (short)(object)right; if (typeof(TKey) == typeof(uint)) - return (uint)(object)left < (uint)(object)right ? true : false; + return (uint)(object)left < (uint)(object)right; if (typeof(TKey) == typeof(int)) - return (int)(object)left < (int)(object)right ? true : false; + return (int)(object)left < (int)(object)right; if (typeof(TKey) == typeof(ulong)) - return (ulong)(object)left < (ulong)(object)right ? true : false; + return (ulong)(object)left < (ulong)(object)right; if (typeof(TKey) == typeof(long)) - return (long)(object)left < (long)(object)right ? true : false; + return (long)(object)left < (long)(object)right; if (typeof(TKey) == typeof(UIntPtr)) - return (nuint)(object)left < (nuint)(object)right ? true : false; + return (nuint)(object)left < (nuint)(object)right; if (typeof(TKey) == typeof(IntPtr)) - return (nint)(object)left < (nint)(object)right ? true : false; + return (nint)(object)left < (nint)(object)right; if (typeof(TKey) == typeof(float)) - return (float)(object)left < (float)(object)right ? true : false; + return (float)(object)left < (float)(object)right; if (typeof(TKey) == typeof(double)) - return (double)(object)left < (double)(object)right ? true : false; + return (double)(object)left < (double)(object)right; #if NET if (typeof(TKey) == typeof(Half)) - return (Half)(object)left < (Half)(object)right ? true : false; + return (Half)(object)left < (Half)(object)right; #endif return left.CompareTo(right) < 0 ? true : false; } @@ -1186,32 +1182,32 @@ private static bool LessThan(ref TKey left, ref TKey right) private static bool GreaterThan(ref TKey left, ref TKey right) { if (typeof(TKey) == typeof(byte)) - return (byte)(object)left > (byte)(object)right ? true : false; + return (byte)(object)left > (byte)(object)right; if (typeof(TKey) == typeof(sbyte)) - return (sbyte)(object)left > (sbyte)(object)right ? true : false; + return (sbyte)(object)left > (sbyte)(object)right; if (typeof(TKey) == typeof(ushort)) - return (ushort)(object)left > (ushort)(object)right ? true : false; + return (ushort)(object)left > (ushort)(object)right; if (typeof(TKey) == typeof(short)) - return (short)(object)left > (short)(object)right ? true : false; + return (short)(object)left > (short)(object)right; if (typeof(TKey) == typeof(uint)) - return (uint)(object)left > (uint)(object)right ? true : false; + return (uint)(object)left > (uint)(object)right; if (typeof(TKey) == typeof(int)) - return (int)(object)left > (int)(object)right ? true : false; + return (int)(object)left > (int)(object)right; if (typeof(TKey) == typeof(ulong)) - return (ulong)(object)left > (ulong)(object)right ? true : false; + return (ulong)(object)left > (ulong)(object)right; if (typeof(TKey) == typeof(long)) - return (long)(object)left > (long)(object)right ? true : false; + return (long)(object)left > (long)(object)right; if (typeof(TKey) == typeof(UIntPtr)) - return (nuint)(object)left > (nuint)(object)right ? true : false; + return (nuint)(object)left > (nuint)(object)right; if (typeof(TKey) == typeof(IntPtr)) - return (nint)(object)left > (nint)(object)right ? true : false; + return (nint)(object)left > (nint)(object)right; if (typeof(TKey) == typeof(float)) - return (float)(object)left > (float)(object)right ? true : false; + return (float)(object)left > (float)(object)right; if (typeof(TKey) == typeof(double)) - return (double)(object)left > (double)(object)right ? true : false; + return (double)(object)left > (double)(object)right; #if NET if (typeof(TKey) == typeof(Half)) - return (Half)(object)left > (Half)(object)right ? true : false; + return (Half)(object)left > (Half)(object)right; #endif return left.CompareTo(right) > 0 ? true : false; } diff --git a/src/Dependencies/Collections/Internal/BitHelper.cs b/src/Dependencies/Collections/Internal/BitHelper.cs index e0164602740fa..f7a74f4f6b806 100644 --- a/src/Dependencies/Collections/Internal/BitHelper.cs +++ b/src/Dependencies/Collections/Internal/BitHelper.cs @@ -3,12 +3,13 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.7/src/libraries/Common/src/System/Collections/Generic/BitHelper.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/src/System/Collections/Generic/BitHelper.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. using System; +using System.Diagnostics; namespace Microsoft.CodeAnalysis.Collections.Internal { @@ -28,19 +29,29 @@ internal BitHelper(Span span, bool clear) internal readonly void MarkBit(int bitPosition) { - var bitArrayIndex = bitPosition / IntSize; - if ((uint)bitArrayIndex < (uint)_span.Length) + Debug.Assert(bitPosition >= 0); + + uint bitArrayIndex = (uint)bitPosition / IntSize; + + // Workaround for https://github.com/dotnet/runtime/issues/72004 + Span span = _span; + if (bitArrayIndex < (uint)span.Length) { - _span[bitArrayIndex] |= (1 << (bitPosition % IntSize)); + span[(int)bitArrayIndex] |= (1 << (int)((uint)bitPosition % IntSize)); } } internal readonly bool IsMarked(int bitPosition) { - var bitArrayIndex = bitPosition / IntSize; + Debug.Assert(bitPosition >= 0); + + uint bitArrayIndex = (uint)bitPosition / IntSize; + + // Workaround for https://github.com/dotnet/runtime/issues/72004 + Span span = _span; return - (uint)bitArrayIndex < (uint)_span.Length && - (_span[bitArrayIndex] & (1 << (bitPosition % IntSize))) != 0; + bitArrayIndex < (uint)span.Length && + (span[(int)bitArrayIndex] & (1 << ((int)((uint)bitPosition % IntSize)))) != 0; } /// How many ints must be allocated to represent n bits. Returns (n+31)/32, but avoids overflow. diff --git a/src/Dependencies/Collections/Internal/HashHelpers.cs b/src/Dependencies/Collections/Internal/HashHelpers.cs index bb10d4ecc16e3..2dc9e630edd63 100644 --- a/src/Dependencies/Collections/Internal/HashHelpers.cs +++ b/src/Dependencies/Collections/Internal/HashHelpers.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Private.CoreLib/src/System/Collections/HashHelpers.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Private.CoreLib/src/System/Collections/HashHelpers.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -17,8 +17,8 @@ namespace Microsoft.CodeAnalysis.Collections.Internal { internal static class HashHelpers { - // This is the maximum prime smaller than Array.MaxArrayLength - public const int MaxPrimeArrayLength = 0x7FEFFFFD; + // This is the maximum prime smaller than Array.MaxLength. + public const int MaxPrimeArrayLength = 0x7FFFFFC3; public const int HashPrime = 101; diff --git a/src/Dependencies/Collections/Internal/ICollectionDebugView`1.cs b/src/Dependencies/Collections/Internal/ICollectionDebugView`1.cs index 28bd3d44a5e76..93269df4e76e6 100644 --- a/src/Dependencies/Collections/Internal/ICollectionDebugView`1.cs +++ b/src/Dependencies/Collections/Internal/ICollectionDebugView`1.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ICollectionDebugView.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ICollectionDebugView.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Dependencies/Collections/Internal/IDictionaryDebugView`2.cs b/src/Dependencies/Collections/Internal/IDictionaryDebugView`2.cs index e39040c8e1a2e..fd92c1c48a8ab 100644 --- a/src/Dependencies/Collections/Internal/IDictionaryDebugView`2.cs +++ b/src/Dependencies/Collections/Internal/IDictionaryDebugView`2.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/IDictionaryDebugView.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/IDictionaryDebugView.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Dependencies/Collections/Internal/InsertionBehavior.cs b/src/Dependencies/Collections/Internal/InsertionBehavior.cs index 1be67a9c8a60e..1e5bcb9bf0623 100644 --- a/src/Dependencies/Collections/Internal/InsertionBehavior.cs +++ b/src/Dependencies/Collections/Internal/InsertionBehavior.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/InsertionBehavior.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/InsertionBehavior.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. diff --git a/src/Dependencies/Collections/Internal/SegmentedHashSetEqualityComparer`1.cs b/src/Dependencies/Collections/Internal/SegmentedHashSetEqualityComparer`1.cs index b9f5dea2e357f..07edb995f83a1 100644 --- a/src/Dependencies/Collections/Internal/SegmentedHashSetEqualityComparer`1.cs +++ b/src/Dependencies/Collections/Internal/SegmentedHashSetEqualityComparer`1.cs @@ -3,12 +3,13 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.7/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSetEqualityComparer.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSetEqualityComparer.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.Collections.Internal { @@ -71,7 +72,7 @@ public int GetHashCode(SegmentedHashSet? obj) { if (t != null) { - hashCode ^= t.GetHashCode(); // same hashcode as as default comparer + hashCode ^= t.GetHashCode(); // same hashcode as default comparer } } } @@ -80,7 +81,7 @@ public int GetHashCode(SegmentedHashSet? obj) } // Equals method for the comparer itself. - public override bool Equals(object? obj) => obj is SegmentedHashSetEqualityComparer; + public override bool Equals([NotNullWhen(true)] object? obj) => obj is SegmentedHashSetEqualityComparer; public override int GetHashCode() => EqualityComparer.Default.GetHashCode(); } diff --git a/src/Dependencies/Collections/Internal/Strings.resx b/src/Dependencies/Collections/Internal/Strings.resx index 4f14f2c7771ee..475ebf866bcc8 100644 --- a/src/Dependencies/Collections/Internal/Strings.resx +++ b/src/Dependencies/Collections/Internal/Strings.resx @@ -144,7 +144,7 @@ An item with the same key has already been added. Key: {0} - + Target array type is not compatible with the type of items in the collection. @@ -159,7 +159,7 @@ Count must be positive and count must refer to a location within the string/array/collection. - + Index was out of range. Must be non-negative and less than the size of the collection. @@ -204,4 +204,7 @@ Cannot find the old value + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + \ No newline at end of file diff --git a/src/Dependencies/Collections/Internal/ThrowHelper.cs b/src/Dependencies/Collections/Internal/ThrowHelper.cs index 3a23a16aeedb6..113f80cda3a20 100644 --- a/src/Dependencies/Collections/Internal/ThrowHelper.cs +++ b/src/Dependencies/Collections/Internal/ThrowHelper.cs @@ -57,10 +57,17 @@ internal static void ThrowArgumentOutOfRangeException() } [DoesNotReturn] - internal static void ThrowArgumentOutOfRange_IndexException() + internal static void ThrowArgumentOutOfRange_IndexMustBeLessException() { throw GetArgumentOutOfRangeException(ExceptionArgument.index, - ExceptionResource.ArgumentOutOfRange_Index); + ExceptionResource.ArgumentOutOfRange_IndexMustBeLess); + } + + [DoesNotReturn] + internal static void ThrowArgumentOutOfRange_IndexMustBeLessOrEqualException() + { + throw GetArgumentOutOfRangeException(ExceptionArgument.index, + ExceptionResource.ArgumentOutOfRange_IndexMustBeLessOrEqual); } [DoesNotReturn] @@ -84,10 +91,17 @@ internal static void ThrowLengthArgumentOutOfRange_ArgumentOutOfRange_NeedNonNeg } [DoesNotReturn] - internal static void ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index() + internal static void ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_IndexMustBeLessOrEqual() + { + throw GetArgumentOutOfRangeException(ExceptionArgument.startIndex, + ExceptionResource.ArgumentOutOfRange_IndexMustBeLessOrEqual); + } + + [DoesNotReturn] + internal static void ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_IndexMustBeLess() { throw GetArgumentOutOfRangeException(ExceptionArgument.startIndex, - ExceptionResource.ArgumentOutOfRange_Index); + ExceptionResource.ArgumentOutOfRange_IndexMustBeLess); } [DoesNotReturn] @@ -172,9 +186,9 @@ internal static void ThrowNotSupportedException(ExceptionResource resource) } [DoesNotReturn] - internal static void ThrowArgumentException_Argument_InvalidArrayType() + internal static void ThrowArgumentException_Argument_IncompatibleArrayType() { - throw new ArgumentException(SR.Argument_InvalidArrayType); + throw new ArgumentException(SR.Argument_IncompatibleArrayType); } [DoesNotReturn] @@ -283,8 +297,10 @@ private static string GetResourceString(ExceptionResource resource) { switch (resource) { - case ExceptionResource.ArgumentOutOfRange_Index: - return SR.ArgumentOutOfRange_Index; + case ExceptionResource.ArgumentOutOfRange_IndexMustBeLessOrEqual: + return SR.ArgumentOutOfRange_IndexMustBeLessOrEqual; + case ExceptionResource.ArgumentOutOfRange_IndexMustBeLess: + return SR.ArgumentOutOfRange_IndexMustBeLess; case ExceptionResource.ArgumentOutOfRange_Count: return SR.ArgumentOutOfRange_Count; case ExceptionResource.Arg_ArrayPlusOffTooSmall: @@ -347,7 +363,8 @@ internal enum ExceptionArgument // internal enum ExceptionResource { - ArgumentOutOfRange_Index, + ArgumentOutOfRange_IndexMustBeLessOrEqual, + ArgumentOutOfRange_IndexMustBeLess, ArgumentOutOfRange_Count, Arg_ArrayPlusOffTooSmall, Arg_RankMultiDimNotSupported, diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.cs.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.cs.xlf index edeeca15d8c76..375ca7d47e5fb 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.cs.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.cs.xlf @@ -67,11 +67,16 @@ Počet musí být kladný a musí odkazovat na umístění v řetězci, v poli nebo v kolekci. - + Index was out of range. Must be non-negative and less than the size of the collection. Index je mimo rozsah. Index musí být nezáporný a musí být menší než velikost kolekce. + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. Index musí být v rozsahu objektu List. @@ -92,7 +97,7 @@ Položka se stejným klíčem již byla přidána. Klíč: {0} - + Target array type is not compatible with the type of items in the collection. Typ cílového pole není kompatibilní s typem položek v kolekci. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.de.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.de.xlf index 2f1d65a9ce3a8..65facbfee1750 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.de.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.de.xlf @@ -67,11 +67,16 @@ Die Anzahl muss positiv sein und auf eine Position in der dem Zeichenfolge/Array/Sammlung verweisen. - + Index was out of range. Must be non-negative and less than the size of the collection. Der Index lag außerhalb des Bereichs. Er darf nicht negativ und kleiner als die Sammlung sein. + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. Der Index muss sich innerhalb der Listenbegrenzung befinden. @@ -92,7 +97,7 @@ Es wurde bereits ein Element mit dem gleichen Schlüssel hinzugefügt. Schlüssel: {0} - + Target array type is not compatible with the type of items in the collection. Der Typ des Zielarrays ist mit dem Typ der Elemente in der Sammlung nicht kompatibel. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.es.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.es.xlf index 1834d9b445bc4..cdb81a8ac2d40 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.es.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.es.xlf @@ -67,11 +67,16 @@ El recuento debe ser positivo y debe hacer referencia a una ubicación en la cadena, matriz o colección. - + Index was out of range. Must be non-negative and less than the size of the collection. El índice estaba fuera del intervalo. Debe ser un valor no negativo e inferior al tamaño de la colección. + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. El índice debe estar dentro de los límites de la lista. @@ -92,7 +97,7 @@ Ya se agregó un elemento con la misma clave. Clave: {0} - + Target array type is not compatible with the type of items in the collection. El tipo de matriz de destino no es compatible con el tipo de elementos de la colección. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.fr.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.fr.xlf index 385fa172ef22e..83d6bdba14806 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.fr.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.fr.xlf @@ -67,11 +67,16 @@ Le compte doit être positif et faire référence à un emplacement de la chaîne/du tableau/de la collection. - + Index was out of range. Must be non-negative and less than the size of the collection. L'index était hors limites. Il ne doit pas être négatif et doit être inférieur à la taille de la collection. + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. L'index doit être dans les limites de la List. @@ -92,7 +97,7 @@ Un élément avec la même clé a déjà été ajouté. Clé : {0} - + Target array type is not compatible with the type of items in the collection. Le type de tableau cible n'est pas compatible avec le type des éléments de la collection. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.it.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.it.xlf index 62104b8bce6b1..f3f86e5685cd9 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.it.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.it.xlf @@ -67,11 +67,16 @@ Il contatore deve avere valore positivo e fare riferimento a una posizione all'interno della stringa, della matrice o della raccolta. - + Index was out of range. Must be non-negative and less than the size of the collection. Indice non compreso nell'intervallo consentito. Deve essere non negativo e minore della dimensione della raccolta. + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. L'indice deve essere compreso nei limiti dell'elenco. @@ -92,7 +97,7 @@ È stato già aggiunto un elemento con la stessa chiave. Chiave: {0} - + Target array type is not compatible with the type of items in the collection. Il tipo di matrice di destinazione non è compatibile con il tipo di elementi della raccolta. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.ja.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.ja.xlf index 97b76467ff6e5..3c38718ac16ab 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.ja.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.ja.xlf @@ -67,11 +67,16 @@ カウントは正の数で、文字列/配列/コレクション内の場所を参照しなければなりません。 - + Index was out of range. Must be non-negative and less than the size of the collection. インデックスが範囲を超えています。負でない値で、コレクションのサイズよりも小さくなければなりません。 + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. インデックスは一覧の範囲内になければなりません。 @@ -92,7 +97,7 @@ 同一のキーを含む項目が既に追加されています。キー: {0} - + Target array type is not compatible with the type of items in the collection. ターゲット配列の型と、コレクション内の項目の型とに互換性がありません。 diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.ko.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.ko.xlf index b781fa48f8ae7..a892561e83dd6 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.ko.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.ko.xlf @@ -67,11 +67,16 @@ 개수는 양수여야 하고 문자열/배열/컬렉션 내의 위치를 참조해야 합니다. - + Index was out of range. Must be non-negative and less than the size of the collection. 인덱스가 범위를 벗어났습니다. 인덱스는 음수가 아니어야 하며 컬렉션의 크기보다 작아야 합니다. + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. 인덱스는 목록의 범위 내에 있어야 합니다. @@ -92,7 +97,7 @@ 동일한 키를 사용하는 항목이 이미 추가되었습니다. 키: {0} - + Target array type is not compatible with the type of items in the collection. 대상 배열 형식이 컬렉션의 항목 형식과 호환되지 않습니다. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.pl.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.pl.xlf index 3f7df61f2b6a8..7d32d95f4abd4 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.pl.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.pl.xlf @@ -67,11 +67,16 @@ Wartość licznika musi być dodatnia i musi odwoływać się do lokalizacji w ciągu/tablicy/kolekcji. - + Index was out of range. Must be non-negative and less than the size of the collection. Indeks był spoza zakresu. Musi mieć wartość nieujemną i mniejszą niż rozmiar kolekcji. + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. Indeks i długość muszą mieścić się w granicach elementu Lista. @@ -92,7 +97,7 @@ Element o tym samym kluczu został już dodany. Klucz: {0} - + Target array type is not compatible with the type of items in the collection. Typ tablicy docelowej nie jest zgodny z typem elementów w kolekcji. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.pt-BR.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.pt-BR.xlf index 2b3b9e611c773..4bbc7ae911d03 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.pt-BR.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.pt-BR.xlf @@ -67,11 +67,16 @@ A contagem deve ser positiva e deve se referir a um local dentro da cadeia de caracteres/matriz/coleção. - + Index was out of range. Must be non-negative and less than the size of the collection. O índice estava fora do intervalo. Deve ser não-negativo e menor do que o tamanho da coleção. + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. O índice deve estar dentro dos limites da Lista. @@ -92,7 +97,7 @@ Já foi adicionado um item com a mesma chave. Chave: {0} - + Target array type is not compatible with the type of items in the collection. O tipo da matriz de destino não é compatível com o tipo dos itens na coleção. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.ru.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.ru.xlf index e773b982ae28e..eb1e1de8236d9 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.ru.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.ru.xlf @@ -67,11 +67,16 @@ Номер должен быть положительным и указывать на местоположение внутри строки/массива/коллекции. - + Index was out of range. Must be non-negative and less than the size of the collection. Индекс за пределами диапазона. Индекс должен быть положительным числом, а его размер не должен превышать размер коллекции. + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. Индекс должен находиться в границах этого списка. @@ -92,7 +97,7 @@ Элемент с таким же ключом уже добавлен. Ключ: {0} - + Target array type is not compatible with the type of items in the collection. Тип целевого массива несовместим с типом элементов в коллекции. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.tr.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.tr.xlf index d5aad38dbba58..bd16ee2cd4c78 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.tr.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.tr.xlf @@ -67,11 +67,16 @@ Sayı pozitif olmalı ve dize/dizi/koleksiyon içinde bir konuma başvurmalıdır. - + Index was out of range. Must be non-negative and less than the size of the collection. Dizin aralık dışındaydı. Negatif bir değer olmamalı ve koleksiyonun boyutundan daha küçük olmalıdır. + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. Dizin, Liste sınırları içinde olmalıdır. @@ -92,7 +97,7 @@ Aynı anahtara sahip bir öğe zaten eklenmiş. Anahtar: {0} - + Target array type is not compatible with the type of items in the collection. Hedef dizi türü, koleksiyondaki öğelerin türüyle uyumlu değil. diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hans.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hans.xlf index 61b6a55418b66..ec7db036395fb 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hans.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hans.xlf @@ -67,11 +67,16 @@ 计数必须为正,且计数必须引用 string/array/collection 内的位置。 - + Index was out of range. Must be non-negative and less than the size of the collection. 索引超出范围。必须为非负值并小于集合大小。 + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. 索引必须位于该列表的界限内。 @@ -92,7 +97,7 @@ 已添加了具有相同键的项。键: {0} - + Target array type is not compatible with the type of items in the collection. 目标数组类型与集合中的项类型不兼容。 diff --git a/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hant.xlf b/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hant.xlf index e01ef076ee0a4..3141c5b2bb8a9 100644 --- a/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hant.xlf +++ b/src/Dependencies/Collections/Internal/xlf/Strings.zh-Hant.xlf @@ -67,11 +67,16 @@ 計數必須為正且計數必須參考字串/陣列/集合中的位置。 - + Index was out of range. Must be non-negative and less than the size of the collection. 索引超出範圍。必須為非負數且小於集合的大小。 + + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + Index was out of range. Must be non-negative and less than or equal to the size of the collection. + + Index must be within the bounds of the List. 索引必須在清單的界限之內。 @@ -92,7 +97,7 @@ 已經新增具有相同索引鍵的項目。索引鍵: {0} - + Target array type is not compatible with the type of items in the collection. 目標陣列類型與集合中項目的類型不相容。 diff --git a/src/Dependencies/Collections/SegmentedArray.cs b/src/Dependencies/Collections/SegmentedArray.cs index b63ad2234be62..41b2fd7b96f47 100644 --- a/src/Dependencies/Collections/SegmentedArray.cs +++ b/src/Dependencies/Collections/SegmentedArray.cs @@ -13,6 +13,12 @@ namespace Microsoft.CodeAnalysis.Collections { internal static class SegmentedArray { +#if NET6_0_OR_GREATER + /// +#endif + internal static void Clear(SegmentedArray array) + => Clear(array, 0, array.Length); + /// internal static void Clear(SegmentedArray array, int index, int length) { @@ -209,7 +215,7 @@ public static int IndexOf(SegmentedArray array, T value, int startIndex, i { if ((uint)startIndex > (uint)array.Length) { - ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index(); + ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_IndexMustBeLessOrEqual(); } if ((uint)count > (uint)(array.Length - startIndex)) @@ -278,7 +284,7 @@ public static int LastIndexOf(SegmentedArray array, T value, int startInde // accept -1 and 0 as valid startIndex for compatibility reason. if (startIndex is not (-1) and not 0) { - ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index(); + ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_IndexMustBeLess(); } // only 0 is a valid value for count if array is empty @@ -293,7 +299,7 @@ public static int LastIndexOf(SegmentedArray array, T value, int startInde // Make sure we're not out of range if ((uint)startIndex >= (uint)array.Length) { - ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index(); + ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_IndexMustBeLess(); } // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0. diff --git a/src/Dependencies/Collections/SegmentedDictionary`2.cs b/src/Dependencies/Collections/SegmentedDictionary`2.cs index 225530726cc93..aead46ec403a5 100644 --- a/src/Dependencies/Collections/SegmentedDictionary`2.cs +++ b/src/Dependencies/Collections/SegmentedDictionary`2.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -14,6 +14,8 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; using Microsoft.CodeAnalysis.Collections.Internal; namespace Microsoft.CodeAnalysis.Collections @@ -39,6 +41,8 @@ private const bool SupportsComparerDevirtualization = false; #endif + private static IEnumerator>? s_emptyEnumerator; + private SegmentedArray _buckets; private SegmentedArray _entries; private ulong _fastModMultiplier; @@ -86,7 +90,19 @@ public SegmentedDictionary(int capacity, IEqualityComparer? comparer) Initialize(capacity); } - if (comparer != null && comparer != EqualityComparer.Default) // first check for null to avoid forcing default comparer instantiation unnecessarily + // For reference types, we always want to store a comparer instance, either + // the one provided, or if one wasn't provided, the default (accessing + // EqualityComparer.Default with shared generics on every dictionary + // access can add measurable overhead). For value types, if no comparer is + // provided, or if the default is provided, we'd prefer to use + // EqualityComparer.Default.Equals on every use, enabling the JIT to + // devirtualize and possibly inline the operation. + if (!typeof(TKey).IsValueType) + { + _comparer = comparer ?? EqualityComparer.Default; + } + else if (comparer is not null && // first check for null to avoid forcing default comparer instantiation unnecessarily + comparer != EqualityComparer.Default) { _comparer = comparer; } @@ -103,52 +119,99 @@ public SegmentedDictionary(IDictionary dictionary) } public SegmentedDictionary(IDictionary dictionary, IEqualityComparer? comparer) - : this(dictionary != null ? dictionary.Count : 0, comparer) + : this(dictionary?.Count ?? 0, comparer) { if (dictionary == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary); } - // It is likely that the passed-in dictionary is SegmentedDictionary. When this is the case, + AddRange(dictionary); + } + + public SegmentedDictionary(IEnumerable> collection) + : this(collection, null) + { + } + + public SegmentedDictionary(IEnumerable> collection, IEqualityComparer? comparer) + : this((collection as ICollection>)?.Count ?? 0, comparer) + { + if (collection == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + } + + AddRange(collection); + } + + private void AddRange(IEnumerable> enumerable) + { + // It is likely that the passed-in enumerable is SegmentedDictionary. When this is the case, // avoid the enumerator allocation and overhead by looping through the entries array directly. // We only do this when dictionary is SegmentedDictionary and not a subclass, to maintain // back-compat with subclasses that may have overridden the enumerator behavior. - if (dictionary.GetType() == typeof(SegmentedDictionary)) + if (enumerable.GetType() == typeof(SegmentedDictionary)) { - var d = (SegmentedDictionary)dictionary; - var count = d._count; - var entries = d._entries; + var source = (SegmentedDictionary)enumerable; + + if (source.Count == 0) + { + // Nothing to copy, all done + return; + } + + // This is not currently a true .AddRange as it needs to be an initialized dictionary + // of the correct size, and also an empty dictionary with no current entities (and no argument checks). + Debug.Assert(_entries.Length >= source.Count); + Debug.Assert(_count == 0); + + var oldEntries = source._entries; + if (source._comparer == _comparer) + { + // If comparers are the same, we can copy _entries without rehashing. + CopyEntries(oldEntries, source._count); + return; + } + + // Comparers differ need to rehash all the entries via Add + var count = source._count; for (var i = 0; i < count; i++) { - if (entries[i]._next >= -1) + // Only copy if an entry + if (oldEntries[i]._next >= -1) { - Add(entries[i]._key, entries[i]._value); + Add(oldEntries[i]._key, oldEntries[i]._value); } } return; } - foreach (var pair in dictionary) + // We similarly special-case KVP<>[] and List>, as they're commonly used to seed dictionaries, and + // we want to avoid the enumerator costs (e.g. allocation) for them as well. Extract a span if possible. + ReadOnlySpan> span; + if (enumerable is KeyValuePair[] array) { - Add(pair.Key, pair.Value); + span = array; } - } - - public SegmentedDictionary(IEnumerable> collection) - : this(collection, null) - { - } - - public SegmentedDictionary(IEnumerable> collection, IEqualityComparer? comparer) - : this((collection as ICollection>)?.Count ?? 0, comparer) - { - if (collection == null) +#if NET5_0_OR_GREATER + else if (enumerable.GetType() == typeof(List>)) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + span = CollectionsMarshal.AsSpan((List>)enumerable); + } +#endif + else + { + // Fallback path for all other enumerables + foreach (var pair in enumerable) + { + Add(pair.Key, pair.Value); + } + return; } - foreach (var pair in collection) + // We got a span. Add the elements to the dictionary. + foreach (var pair in span) { Add(pair.Key, pair.Value); } @@ -236,7 +299,7 @@ public void Clear() Debug.Assert(_buckets.Length > 0, "_buckets should be non-empty"); Debug.Assert(_entries.Length > 0, "_entries should be non-empty"); - SegmentedArray.Clear(_buckets, 0, _buckets.Length); + SegmentedArray.Clear(_buckets); _count = 0; _freeList = -1; @@ -321,8 +384,14 @@ private void CopyTo(KeyValuePair[] array, int index) public Enumerator GetEnumerator() => new(this, Enumerator.KeyValuePair); - IEnumerator> IEnumerable>.GetEnumerator() - => new Enumerator(this, Enumerator.KeyValuePair); + IEnumerator> IEnumerable>.GetEnumerator() => + Count == 0 ? GetEmptyEnumerator() : + GetEnumerator(); + + private static IEnumerator> GetEmptyEnumerator() + { + return LazyInitializer.EnsureInitialized(ref s_emptyEnumerator, static () => new Enumerator(new SegmentedDictionary(), Enumerator.KeyValuePair))!; + } private ref TValue FindValue(TKey key) { @@ -336,77 +405,45 @@ private ref TValue FindValue(TKey key) { Debug.Assert(_entries.Length > 0, "expected entries to be non-empty"); var comparer = _comparer; - if (SupportsComparerDevirtualization && comparer == null) + if (SupportsComparerDevirtualization + && typeof(TKey).IsValueType // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types + && comparer == null) { var hashCode = (uint)key.GetHashCode(); var i = GetBucket(hashCode); var entries = _entries; uint collisionCount = 0; - if (typeof(TKey).IsValueType) - { - // ValueType: Devirtualize with EqualityComparer.Default intrinsic - - i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. - do - { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 - // Test in if to drop range check for following array access - if ((uint)i >= (uint)entries.Length) - { - goto ReturnNotFound; - } - - entry = ref entries[i]; - if (entry._hashCode == hashCode && EqualityComparer.Default.Equals(entry._key, key)) - { - goto ReturnFound; - } - i = entry._next; - - collisionCount++; - } while (collisionCount <= (uint)entries.Length); - - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - goto ConcurrentOperation; - } - else + // ValueType: Devirtualize with EqualityComparer.Default intrinsic + i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. + do { - // Object type: Shared Generic, EqualityComparer.Default won't devirtualize - // https://github.com/dotnet/runtime/issues/10050 - // So cache in a local rather than get EqualityComparer per loop iteration - var defaultComparer = EqualityComparer.Default; - - i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. - do + // Should be a while loop https://github.com/dotnet/runtime/issues/9422 + // Test in if to drop range check for following array access + if ((uint)i >= (uint)entries.Length) { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 - // Test in if to drop range check for following array access - if ((uint)i >= (uint)entries.Length) - { - goto ReturnNotFound; - } + goto ReturnNotFound; + } - entry = ref entries[i]; - if (entry._hashCode == hashCode && defaultComparer.Equals(entry._key, key)) - { - goto ReturnFound; - } + entry = ref entries[i]; + if (entry._hashCode == hashCode && EqualityComparer.Default.Equals(entry._key, key)) + { + goto ReturnFound; + } - i = entry._next; + i = entry._next; - collisionCount++; - } while (collisionCount <= (uint)entries.Length); + collisionCount++; + } while (collisionCount <= (uint)entries.Length); - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - goto ConcurrentOperation; - } + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + goto ConcurrentOperation; } else { - var hashCode = (uint)comparer.GetHashCode(key); + Debug.Assert(comparer is not null); + var hashCode = (uint)comparer!.GetHashCode(key); var i = GetBucket(hashCode); var entries = _entries; uint collisionCount = 0; @@ -482,98 +519,57 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) Debug.Assert(entries.Length > 0, "expected entries to be non-empty"); var comparer = _comparer; - var hashCode = (uint)((SupportsComparerDevirtualization && comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)); + Debug.Assert(comparer is not null || (SupportsComparerDevirtualization && typeof(TKey).IsValueType)); + var hashCode = (uint)((SupportsComparerDevirtualization && typeof(TKey).IsValueType && 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 (SupportsComparerDevirtualization && comparer == null) + if (SupportsComparerDevirtualization + && typeof(TKey).IsValueType // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types + && comparer == null) { - if (typeof(TKey).IsValueType) + // ValueType: Devirtualize with EqualityComparer.Default intrinsic + while (true) { - // ValueType: Devirtualize with EqualityComparer.Default intrinsic - while (true) + // Should be a while loop https://github.com/dotnet/runtime/issues/9422 + // Test uint in if rather than loop condition to drop range check for following array access + if ((uint)i >= (uint)entries.Length) { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 - // Test uint in if rather than loop condition to drop range check for following array access - if ((uint)i >= (uint)entries.Length) - { - break; - } - - if (entries[i]._hashCode == hashCode && EqualityComparer.Default.Equals(entries[i]._key, key)) - { - if (behavior == InsertionBehavior.OverwriteExisting) - { - entries[i]._value = value; - return true; - } - - if (behavior == InsertionBehavior.ThrowOnExisting) - { - ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); - } - - return false; - } - - i = entries[i]._next; - - collisionCount++; - if (collisionCount > (uint)entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); - } + break; } - } - else - { - // Object type: Shared Generic, EqualityComparer.Default won't devirtualize - // https://github.com/dotnet/runtime/issues/10050 - // So cache in a local rather than get EqualityComparer per loop iteration - var defaultComparer = EqualityComparer.Default; - while (true) + + if (entries[i]._hashCode == hashCode && EqualityComparer.Default.Equals(entries[i]._key, key)) { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 - // Test uint in if rather than loop condition to drop range check for following array access - if ((uint)i >= (uint)entries.Length) + if (behavior == InsertionBehavior.OverwriteExisting) { - break; + entries[i]._value = value; + return true; } - if (entries[i]._hashCode == hashCode && defaultComparer.Equals(entries[i]._key, key)) + if (behavior == InsertionBehavior.ThrowOnExisting) { - if (behavior == InsertionBehavior.OverwriteExisting) - { - entries[i]._value = value; - return true; - } - - if (behavior == InsertionBehavior.ThrowOnExisting) - { - ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); - } - - return false; + ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); } - i = entries[i]._next; + return false; + } + + i = entries[i]._next; - collisionCount++; - if (collisionCount > (uint)entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); - } + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); } } } else { + Debug.Assert(comparer is not null); while (true) { // Should be a while loop https://github.com/dotnet/runtime/issues/9422 @@ -583,7 +579,7 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) break; } - if (entries[i]._hashCode == hashCode && comparer.Equals(entries[i]._key, key)) + if (entries[i]._hashCode == hashCode && comparer!.Equals(entries[i]._key, key)) { if (behavior == InsertionBehavior.OverwriteExisting) { @@ -636,8 +632,8 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) entry._hashCode = hashCode; entry._next = bucket - 1; // Value in _buckets is 1-based entry._key = key; - entry._value = value; // Value in _buckets is 1-based - bucket = index + 1; + entry._value = value; + bucket = index + 1; // Value in _buckets is 1-based _version++; return true; } @@ -686,7 +682,11 @@ public bool Remove(TKey key) { Debug.Assert(_entries.Length > 0, "entries should be non-empty"); uint collisionCount = 0; - var hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode()); + + var comparer = _comparer; + Debug.Assert((SupportsComparerDevirtualization && typeof(TKey).IsValueType) || comparer is not null); + var hashCode = (uint)(SupportsComparerDevirtualization && typeof(TKey).IsValueType && comparer == null ? key.GetHashCode() : comparer!.GetHashCode(key)); + ref var bucket = ref GetBucket(hashCode); var entries = _entries; var last = -1; @@ -695,7 +695,8 @@ public bool Remove(TKey key) { ref var entry = ref entries[i]; - if (entry._hashCode == hashCode && (_comparer?.Equals(entry._key, key) ?? EqualityComparer.Default.Equals(entry._key, key))) + if (entry._hashCode == hashCode && + (SupportsComparerDevirtualization && typeof(TKey).IsValueType && comparer == null ? EqualityComparer.Default.Equals(entry._key, key) : comparer!.Equals(entry._key, key))) { if (last < 0) { @@ -758,7 +759,11 @@ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value) { Debug.Assert(_entries.Length > 0, "entries should be non-empty"); uint collisionCount = 0; - var hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode()); + + var comparer = _comparer; + Debug.Assert((SupportsComparerDevirtualization && typeof(TKey).IsValueType) || comparer is not null); + var hashCode = (uint)(SupportsComparerDevirtualization && typeof(TKey).IsValueType && comparer == null ? key.GetHashCode() : comparer!.GetHashCode(key)); + ref var bucket = ref GetBucket(hashCode); var entries = _entries; var last = -1; @@ -767,7 +772,8 @@ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value) { ref var entry = ref entries[i]; - if (entry._hashCode == hashCode && (_comparer?.Equals(entry._key, key) ?? EqualityComparer.Default.Equals(entry._key, key))) + if (entry._hashCode == hashCode && + (SupportsComparerDevirtualization && typeof(TKey).IsValueType && comparer == null ? EqualityComparer.Default.Equals(entry._key, key) : comparer!.Equals(entry._key, key))) { if (last < 0) { @@ -889,7 +895,7 @@ void ICollection.CopyTo(Array array, int index) var objects = array as object[]; if (objects == null) { - ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); } try @@ -906,13 +912,13 @@ void ICollection.CopyTo(Array array, int index) } catch (ArrayTypeMismatchException) { - ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); } } } IEnumerator IEnumerable.GetEnumerator() - => new Enumerator(this, Enumerator.KeyValuePair); + => ((IEnumerable>)this).GetEnumerator(); /// /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage @@ -982,23 +988,29 @@ public void TrimExcess(int capacity) var oldCount = _count; _version++; Initialize(newSize); - var entries = _entries; - var count = 0; - for (var i = 0; i < oldCount; i++) + + CopyEntries(oldEntries, oldCount); + } + + private void CopyEntries(SegmentedArray entries, int count) + { + var newEntries = _entries; + var newCount = 0; + for (var i = 0; i < count; i++) { - var hashCode = oldEntries[i]._hashCode; // At this point, we know we have entries. - if (oldEntries[i]._next >= -1) + var hashCode = entries[i]._hashCode; + if (entries[i]._next >= -1) { - ref var entry = ref entries[count]; - entry = oldEntries[i]; + ref var entry = ref newEntries[newCount]; + entry = entries[i]; ref var bucket = ref GetBucket(hashCode); entry._next = bucket - 1; // Value in _buckets is 1-based - bucket = count + 1; - count++; + bucket = newCount + 1; + newCount++; } } - _count = count; + _count = newCount; _freeCount = 0; } @@ -1257,6 +1269,8 @@ readonly object? IDictionaryEnumerator.Value [DebuggerDisplay("Count = {Count}")] public sealed class KeyCollection : ICollection, ICollection, IReadOnlyCollection { + private static IEnumerator? s_emptyEnumerator; + private readonly SegmentedDictionary _dictionary; public KeyCollection(SegmentedDictionary dictionary) @@ -1308,7 +1322,7 @@ void ICollection.Add(TKey item) void ICollection.Clear() => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet); - bool ICollection.Contains(TKey item) + public bool Contains(TKey item) => _dictionary.ContainsKey(item); bool ICollection.Remove(TKey item) @@ -1317,11 +1331,17 @@ bool ICollection.Remove(TKey item) return false; } - IEnumerator IEnumerable.GetEnumerator() - => new Enumerator(_dictionary); + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? GetEmptyEnumerator() : + GetEnumerator(); + + private static IEnumerator GetEmptyEnumerator() + { + return LazyInitializer.EnsureInitialized(ref s_emptyEnumerator, static () => new Enumerator(new SegmentedDictionary()))!; + } IEnumerator IEnumerable.GetEnumerator() - => new Enumerator(_dictionary); + => ((IEnumerable)this).GetEnumerator(); void ICollection.CopyTo(Array array, int index) { @@ -1359,7 +1379,7 @@ void ICollection.CopyTo(Array array, int index) var objects = array as object[]; if (objects == null) { - ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); } var count = _dictionary._count; @@ -1374,7 +1394,7 @@ void ICollection.CopyTo(Array array, int index) } catch (ArrayTypeMismatchException) { - ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); } } } @@ -1457,6 +1477,8 @@ void IEnumerator.Reset() [DebuggerDisplay("Count = {Count}")] public sealed class ValueCollection : ICollection, ICollection, IReadOnlyCollection { + private static IEnumerator? s_emptyEnumerator; + private readonly SegmentedDictionary _dictionary; public ValueCollection(SegmentedDictionary dictionary) @@ -1517,11 +1539,17 @@ void ICollection.Clear() bool ICollection.Contains(TValue item) => _dictionary.ContainsValue(item); - IEnumerator IEnumerable.GetEnumerator() - => new Enumerator(_dictionary); + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? GetEmptyEnumerator() : + GetEnumerator(); + + private static IEnumerator GetEmptyEnumerator() + { + return LazyInitializer.EnsureInitialized(ref s_emptyEnumerator, static () => new Enumerator(new SegmentedDictionary()))!; + } IEnumerator IEnumerable.GetEnumerator() - => new Enumerator(_dictionary); + => ((IEnumerable)this).GetEnumerator(); void ICollection.CopyTo(Array array, int index) { @@ -1559,7 +1587,7 @@ void ICollection.CopyTo(Array array, int index) var objects = array as object[]; if (objects == null) { - ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); } var count = _dictionary._count; @@ -1574,7 +1602,7 @@ void ICollection.CopyTo(Array array, int index) } catch (ArrayTypeMismatchException) { - ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); } } } diff --git a/src/Dependencies/Collections/SegmentedHashSet`1.cs b/src/Dependencies/Collections/SegmentedHashSet`1.cs index 6fd0c70111950..2e35a5ffae94a 100644 --- a/src/Dependencies/Collections/SegmentedHashSet`1.cs +++ b/src/Dependencies/Collections/SegmentedHashSet`1.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.7/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -14,6 +14,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Threading; using Microsoft.CodeAnalysis.Collections.Internal; namespace Microsoft.CodeAnalysis.Collections @@ -47,6 +48,8 @@ private const bool SupportsComparerDevirtualization private const int ShrinkThreshold = 3; private const int StartOfFreeList = -3; + private static IEnumerator? s_emptyEnumerator; + private SegmentedArray _buckets; private SegmentedArray _entries; private ulong _fastModMultiplier; @@ -70,7 +73,19 @@ public SegmentedHashSet() : this((IEqualityComparer?)null) { } public SegmentedHashSet(IEqualityComparer? comparer) { - if (comparer != null && comparer != EqualityComparer.Default) // first check for null to avoid forcing default comparer instantiation unnecessarily + // For reference types, we always want to store a comparer instance, either + // the one provided, or if one wasn't provided, the default (accessing + // EqualityComparer.Default with shared generics on every dictionary + // access can add measurable overhead). For value types, if no comparer is + // provided, or if the default is provided, we'd prefer to use + // EqualityComparer.Default.Equals on every use, enabling the JIT to + // devirtualize and possibly inline the operation. + if (!typeof(T).IsValueType) + { + _comparer = comparer ?? EqualityComparer.Default; + } + else if (comparer is not null && // first check for null to avoid forcing default comparer instantiation unnecessarily + comparer != EqualityComparer.Default) { _comparer = comparer; } @@ -187,7 +202,7 @@ public void Clear() Debug.Assert(_buckets.Length > 0, "_buckets should be non-empty"); Debug.Assert(_entries.Length > 0, "_entries should be non-empty"); - SegmentedArray.Clear(_buckets, 0, _buckets.Length); + SegmentedArray.Clear(_buckets); _count = 0; _freeList = -1; _freeCount = 0; @@ -212,62 +227,39 @@ private int FindItemIndex(T item) uint collisionCount = 0; var comparer = _comparer; - if (SupportsComparerDevirtualization && comparer == null) + if (SupportsComparerDevirtualization && + typeof(T).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types + comparer == null) { - var hashCode = item != null ? item.GetHashCode() : 0; - if (typeof(T).IsValueType) + // ValueType: Devirtualize with EqualityComparer.Default intrinsic + var hashCode = item!.GetHashCode(); + var i = GetBucketRef(hashCode) - 1; // Value in _buckets is 1-based + while (i >= 0) { - // ValueType: Devirtualize with EqualityComparer.Default intrinsic - var i = GetBucketRef(hashCode) - 1; // Value in _buckets is 1-based - while (i >= 0) + ref var entry = ref entries[i]; + if (entry._hashCode == hashCode && EqualityComparer.Default.Equals(entry._value, item)) { - ref var entry = ref entries[i]; - if (entry._hashCode == hashCode && EqualityComparer.Default.Equals(entry._value, item)) - { - return i; - } - i = entry._next; - - collisionCount++; - if (collisionCount > (uint)entries.Length) - { - // The chain of entries forms a loop, which means a concurrent update has happened. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); - } + return i; } - } - else - { - // Object type: Shared Generic, EqualityComparer.Default won't devirtualize (https://github.com/dotnet/runtime/issues/10050), - // so cache in a local rather than get EqualityComparer per loop iteration. - var defaultComparer = EqualityComparer.Default; - var i = GetBucketRef(hashCode) - 1; // Value in _buckets is 1-based - while (i >= 0) + i = entry._next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) { - ref var entry = ref entries[i]; - if (entry._hashCode == hashCode && defaultComparer.Equals(entry._value, item)) - { - return i; - } - i = entry._next; - - collisionCount++; - if (collisionCount > (uint)entries.Length) - { - // The chain of entries forms a loop, which means a concurrent update has happened. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); - } + // The chain of entries forms a loop, which means a concurrent update has happened. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); } } } else { - var hashCode = item != null ? comparer.GetHashCode(item) : 0; + Debug.Assert(comparer is not null); + var hashCode = item != null ? comparer!.GetHashCode(item) : 0; var i = GetBucketRef(hashCode) - 1; // Value in _buckets is 1-based while (i >= 0) { ref var entry = ref entries[i]; - if (entry._hashCode == hashCode && comparer.Equals(entry._value, item)) + if (entry._hashCode == hashCode && comparer!.Equals(entry._value, item)) { return i; } @@ -303,7 +295,13 @@ public bool Remove(T item) uint collisionCount = 0; var last = -1; - var hashCode = item != null ? (_comparer?.GetHashCode(item) ?? item.GetHashCode()) : 0; + + IEqualityComparer? comparer = _comparer; + Debug.Assert((SupportsComparerDevirtualization && typeof(T).IsValueType) || comparer is not null); + int hashCode = + SupportsComparerDevirtualization && typeof(T).IsValueType && comparer == null ? item!.GetHashCode() : + item is not null ? comparer!.GetHashCode(item) : + 0; ref var bucket = ref GetBucketRef(hashCode); var i = bucket - 1; // Value in buckets is 1-based @@ -312,7 +310,7 @@ public bool Remove(T item) { ref var entry = ref entries[i]; - if (entry._hashCode == hashCode && (_comparer?.Equals(entry._value, item) ?? EqualityComparer.Default.Equals(entry._value, item))) + if (entry._hashCode == hashCode && (comparer?.Equals(entry._value, item) ?? EqualityComparer.Default.Equals(entry._value, item))) { if (last < 0) { @@ -365,9 +363,16 @@ public bool Remove(T item) public Enumerator GetEnumerator() => new(this); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? GetEmptyEnumerator() : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); + + private static IEnumerator GetEmptyEnumerator() + { + return LazyInitializer.EnsureInitialized(ref s_emptyEnumerator, static () => new Enumerator(new SegmentedHashSet()))!; + } #endregion @@ -641,7 +646,15 @@ public bool IsSupersetOf(IEnumerable other) } } - return ContainsAllElements(other); + foreach (T element in other) + { + if (!Contains(element)) + { + return false; + } + } + + return true; } /// Determines whether a object is a proper superset of the specified collection. @@ -678,7 +691,7 @@ public bool IsProperSupersetOf(IEnumerable other) } // Now perform element check. - return ContainsAllElements(otherAsSet); + return otherAsSet.IsSubsetOfHashSetWithSameComparer(this); } } @@ -746,8 +759,8 @@ public bool SetEquals(IEnumerable other) } // Already confirmed that the sets have the same number of distinct elements, so if - // one is a superset of the other then they must be equal. - return ContainsAllElements(otherAsSet); + // one is a subset of the other then they must be equal. + return IsSubsetOfHashSetWithSameComparer(otherAsSet); } else { @@ -778,6 +791,10 @@ public void CopyTo(T[] array, int arrayIndex, int count) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); } +#if NET8_0_OR_GREATER + ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex); + ArgumentOutOfRangeException.ThrowIfNegative(count); +#else // Check array index valid index into array. if (arrayIndex < 0) { @@ -789,6 +806,7 @@ public void CopyTo(T[] array, int arrayIndex, int count) { throw new ArgumentOutOfRangeException(nameof(count), count, SR.ArgumentOutOfRange_NeedNonNegNum); } +#endif // Will the array, starting at arrayIndex, be able to hold elements? Note: not // checking arrayIndex >= array.Length (consistency with list of allowing @@ -989,65 +1007,43 @@ private bool AddIfNotPresent(T value, out int location) uint collisionCount = 0; ref var bucket = ref RoslynUnsafe.NullRef(); - if (SupportsComparerDevirtualization && comparer == null) + if (SupportsComparerDevirtualization && + typeof(T).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types + comparer == null) { - hashCode = value != null ? value.GetHashCode() : 0; + hashCode = value!.GetHashCode(); bucket = ref GetBucketRef(hashCode); var i = bucket - 1; // Value in _buckets is 1-based - if (typeof(T).IsValueType) - { - // ValueType: Devirtualize with EqualityComparer.Default intrinsic - while (i >= 0) - { - ref var entry = ref entries[i]; - if (entry._hashCode == hashCode && EqualityComparer.Default.Equals(entry._value, value)) - { - location = i; - return false; - } - i = entry._next; - collisionCount++; - if (collisionCount > (uint)entries.Length) - { - // The chain of entries forms a loop, which means a concurrent update has happened. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); - } - } - } - else + // ValueType: Devirtualize with EqualityComparer.Default intrinsic + while (i >= 0) { - // Object type: Shared Generic, EqualityComparer.Default won't devirtualize (https://github.com/dotnet/runtime/issues/10050), - // so cache in a local rather than get EqualityComparer per loop iteration. - var defaultComparer = EqualityComparer.Default; - while (i >= 0) + ref var entry = ref entries[i]; + if (entry._hashCode == hashCode && EqualityComparer.Default.Equals(entry._value, value)) { - ref var entry = ref entries[i]; - if (entry._hashCode == hashCode && defaultComparer.Equals(entry._value, value)) - { - location = i; - return false; - } - i = entry._next; + location = i; + return false; + } + i = entry._next; - collisionCount++; - if (collisionCount > (uint)entries.Length) - { - // The chain of entries forms a loop, which means a concurrent update has happened. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); - } + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop, which means a concurrent update has happened. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); } } } else { - hashCode = value != null ? comparer.GetHashCode(value) : 0; + Debug.Assert(comparer is not null); + hashCode = value != null ? comparer!.GetHashCode(value) : 0; bucket = ref GetBucketRef(hashCode); var i = bucket - 1; // Value in _buckets is 1-based while (i >= 0) { ref var entry = ref entries[i]; - if (entry._hashCode == hashCode && comparer.Equals(entry._value, value)) + if (entry._hashCode == hashCode && comparer!.Equals(entry._value, value)) { location = i; return false; @@ -1097,24 +1093,6 @@ private bool AddIfNotPresent(T value, out int location) return true; } - /// - /// Checks if this contains of other's elements. Iterates over other's elements and - /// returns false as soon as it finds an element in other that's not in this. - /// Used by SupersetOf, ProperSupersetOf, and SetEquals. - /// - private bool ContainsAllElements(IEnumerable other) - { - foreach (var element in other) - { - if (!Contains(element)) - { - return false; - } - } - - return true; - } - /// /// Implementation Notes: /// If other is a hashset and is using same equality comparer, then checking subset is @@ -1309,7 +1287,7 @@ private unsafe void SymmetricExceptWithEnumerable(IEnumerable other) /// /// Allows us to finish faster for equals and proper superset /// because unfoundCount must be 0. - private unsafe (int UniqueCount, int UnfoundCount) CheckUniqueAndUnfoundElements(IEnumerable other, bool returnIfUnfound) + private (int UniqueCount, int UnfoundCount) CheckUniqueAndUnfoundElements(IEnumerable other, bool returnIfUnfound) { // Need special case in case this has no elements. if (_count == 0) diff --git a/src/Dependencies/Collections/SegmentedList`1.cs b/src/Dependencies/Collections/SegmentedList`1.cs index 894f102b6318c..bf2bef8821287 100644 --- a/src/Dependencies/Collections/SegmentedList`1.cs +++ b/src/Dependencies/Collections/SegmentedList`1.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // NOTE: This code is derived from an implementation originally in dotnet/runtime: -// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs +// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. @@ -14,6 +14,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Threading; using Microsoft.CodeAnalysis.Collections.Internal; namespace Microsoft.CodeAnalysis.Collections @@ -32,13 +33,14 @@ namespace Microsoft.CodeAnalysis.Collections internal class SegmentedList : IList, IList, IReadOnlyList { private const int DefaultCapacity = 4; - private const int MaxArrayLength = 0x7FEFFFFF; + private const int MaxLength = 0x7FFFFFC7; internal SegmentedArray _items; internal int _size; - private int _version; + internal int _version; private static readonly SegmentedArray s_emptyArray = new(0); + private static IEnumerator? s_emptyEnumerator; // Constructs a SegmentedList. The list is initially empty and has a capacity // of zero. Upon adding the first element to the list the capacity is @@ -172,7 +174,7 @@ public T this[int index] // Following trick can reduce the range check by one if ((uint)index >= (uint)_size) { - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); } return _items[index]; } @@ -181,7 +183,7 @@ public T this[int index] { if ((uint)index >= (uint)_size) { - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); } _items[index] = value; _version++; @@ -238,8 +240,9 @@ public void Add(T item) [MethodImpl(MethodImplOptions.NoInlining)] private void AddWithResize(T item) { + Debug.Assert(_size == _items.Length); var size = _size; - EnsureCapacity(size + 1); + Grow(size + 1); _size = size + 1; _items[size] = item; } @@ -265,7 +268,50 @@ int IList.Add(object? item) // capacity or the new size, whichever is larger. // public void AddRange(IEnumerable collection) - => InsertRange(_size, collection); + { + if (collection == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + } + + if (collection is ICollection c) + { + var count = c.Count; + if (count > 0) + { + if (_items.Length - _size < count) + { + Grow(checked(_size + count)); + } + + if (c is SegmentedList list) + { + SegmentedArray.Copy(list._items, 0, _items, _size, list.Count); + } + else if (c is SegmentedArray array) + { + SegmentedArray.Copy(array, 0, _items, _size, array.Length); + } + else + { + var targetIndex = _size; + foreach (var item in c) + _items[targetIndex++] = item; + } + + _size += count; + _version++; + } + } + else + { + using var en = collection.GetEnumerator(); + while (en.MoveNext()) + { + Add(en.Current); + } + } + } public ReadOnlyCollection AsReadOnly() => new(this); @@ -299,7 +345,7 @@ public int BinarySearch(int index, int count, T item, IComparer? comparer) if (_size - index < count) ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen); - return SegmentedArray.BinarySearch(_items, index, count, item, comparer); + return SegmentedArray.BinarySearch(_items, index, count, item, comparer); } public int BinarySearch(T item) @@ -343,7 +389,7 @@ public bool Contains(T item) // via EqualityComparer.Default.Equals, we // only make one virtual call to EqualityComparer.IndexOf. - return _size != 0 && IndexOf(item) != -1; + return _size != 0 && IndexOf(item) >= 0; } bool IList.Contains(object? item) @@ -392,7 +438,7 @@ void ICollection.CopyTo(Array array, int arrayIndex) } catch (ArrayTypeMismatchException) { - ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); } } @@ -417,24 +463,48 @@ public void CopyTo(T[] array, int arrayIndex) SegmentedArray.Copy(_items, 0, array, arrayIndex, _size); } - // Ensures that the capacity of this list is at least the given minimum - // value. If the current capacity of the list is less than min, the - // capacity is increased to twice the current capacity or to min, - // whichever is larger. - // - private void EnsureCapacity(int min) + /// + /// Ensures that the capacity of this list is at least the specified . + /// If the current capacity of the list is less than specified , + /// the capacity is increased by continuously twice current capacity until it is at least the specified . + /// + /// The minimum capacity to ensure. + /// The new capacity of this list. + public int EnsureCapacity(int capacity) { - if (_items.Length < min) + if (capacity < 0) { - var newCapacity = _items.Length == 0 ? DefaultCapacity : _items.Length * 2; - // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. - // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newCapacity > MaxArrayLength) - newCapacity = MaxArrayLength; - if (newCapacity < min) - newCapacity = min; - Capacity = newCapacity; + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + if (_items.Length < capacity) + { + Grow(capacity); } + + return _items.Length; + } + + /// + /// Increase the capacity of this list to at least the specified . + /// + /// The minimum capacity to ensure. + internal void Grow(int capacity) + { + Debug.Assert(_items.Length < capacity); + + var newCapacity = _items.Length == 0 ? DefaultCapacity : 2 * _items.Length; + + // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newCapacity > MaxLength) + newCapacity = MaxLength; + + // If the computed capacity is still less than specified, set to the original argument. + // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. + if (newCapacity < capacity) + newCapacity = capacity; + + Capacity = newCapacity; } public bool Exists(Predicate match) @@ -485,7 +555,7 @@ public int FindIndex(int startIndex, int count, Predicate match) { if ((uint)startIndex > (uint)_size) { - ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index(); + ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_IndexMustBeLessOrEqual(); } if (count < 0 || startIndex > _size - count) @@ -542,7 +612,7 @@ public int FindLastIndex(int startIndex, int count, Predicate match) // Special case for 0 length SegmentedList if (startIndex != -1) { - ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index(); + ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_IndexMustBeLess(); } } else @@ -550,7 +620,7 @@ public int FindLastIndex(int startIndex, int count, Predicate match) // Make sure we're not out of range if ((uint)startIndex >= (uint)_size) { - ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index(); + ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_IndexMustBeLess(); } } @@ -598,14 +668,18 @@ public void ForEach(Action action) // while an enumeration is in progress, the MoveNext and // GetObject methods of the enumerator will throw an exception. // - public Enumerator GetEnumerator() - => new(this); + public Enumerator GetEnumerator() => new(this); + + IEnumerator IEnumerable.GetEnumerator() => + Count == 0 ? GetEmptyEnumerator() : + GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - => new Enumerator(this); + private static IEnumerator GetEmptyEnumerator() + { + return LazyInitializer.EnsureInitialized(ref s_emptyEnumerator, static () => new Enumerator(new SegmentedList(0)))!; + } public SegmentedList GetRange(int index, int count) { @@ -630,6 +704,20 @@ public SegmentedList GetRange(int index, int count) return list; } + /// + /// Creates a shallow copy of a range of elements in the source . + /// + /// The zero-based index at which the range starts. + /// The length of the range. + /// A shallow copy of a range of elements in the source . + /// + /// is less than 0. + /// -or- + /// is less than 0. + /// + /// and do not denote a valid range of elements in the . + public SegmentedList Slice(int start, int length) => GetRange(start, length); + // Returns the index of the first occurrence of a given value in a range of // this list. The list is searched forwards from beginning to end. // The elements of the list are compared to the given value using the @@ -662,7 +750,7 @@ int IList.IndexOf(object? item) public int IndexOf(T item, int index) { if (index > _size) - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessOrEqualException(); return SegmentedArray.IndexOf(_items, item, index, _size - index); } @@ -678,7 +766,7 @@ public int IndexOf(T item, int index) public int IndexOf(T item, int index, int count) { if (index > _size) - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessOrEqualException(); if (count < 0 || index > _size - count) ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count(); @@ -689,7 +777,7 @@ public int IndexOf(T item, int index, int count) public int IndexOf(T item, int index, int count, IEqualityComparer? comparer) { if (index > _size) - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessOrEqualException(); if (count < 0 || index > _size - count) ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count(); @@ -709,7 +797,7 @@ public void Insert(int index, T item) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert); } if (_size == _items.Length) - EnsureCapacity(_size + 1); + Grow(_size + 1); if (index < _size) { SegmentedArray.Copy(_items, index, _items, index + 1, _size - index); @@ -747,7 +835,7 @@ public void InsertRange(int index, IEnumerable collection) if ((uint)index > (uint)_size) { - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessOrEqualException(); } if (collection is ICollection c) @@ -755,7 +843,10 @@ public void InsertRange(int index, IEnumerable collection) var count = c.Count; if (count > 0) { - EnsureCapacity(_size + count); + if (_items.Length - _size < count) + { + Grow(checked(_size + count)); + } if (index < _size) { SegmentedArray.Copy(_items, index, _items, index + count, _size - index); @@ -785,6 +876,7 @@ public void InsertRange(int index, IEnumerable collection) } _size += count; + _version++; } } else @@ -795,7 +887,6 @@ public void InsertRange(int index, IEnumerable collection) Insert(index++, en.Current); } } - _version++; } // Returns the index of the last occurrence of a given value in a range of @@ -830,7 +921,7 @@ public int LastIndexOf(T item) public int LastIndexOf(T item, int index) { if (index >= _size) - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); return LastIndexOf(item, index, index + 1); } @@ -905,8 +996,8 @@ public int LastIndexOf(T item, int index, int count, IEqualityComparer? compa return SegmentedArray.LastIndexOf(_items, item, index, count, comparer); } - // Removes the element at the given index. The size of the list is - // decreased by one. + // Removes the first occurrence of the given element, if found. + // The size of the list is decreased by one if successful. public bool Remove(T item) { var index = IndexOf(item); @@ -977,7 +1068,7 @@ public void RemoveAt(int index) { if ((uint)index >= (uint)_size) { - ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); } _size--; if (index < _size) @@ -1093,7 +1184,7 @@ public void Sort(int index, int count, IComparer? comparer) if (count > 1) { - SegmentedArray.Sort(_items, index, count, comparer); + SegmentedArray.Sort(_items, index, count, comparer); } _version++; } diff --git a/src/Dependencies/PooledObjects/ArrayBuilder.cs b/src/Dependencies/PooledObjects/ArrayBuilder.cs index c78ef000190a2..b0246977a88b4 100644 --- a/src/Dependencies/PooledObjects/ArrayBuilder.cs +++ b/src/Dependencies/PooledObjects/ArrayBuilder.cs @@ -263,6 +263,30 @@ public void RemoveLast() _builder.RemoveAt(_builder.Count - 1); } + public void RemoveAll(Predicate match) + { + _builder.RemoveAll(match); + } + + public void RemoveAll(Func match, TArg arg) + { + var i = 0; + for (var j = 0; j < _builder.Count; j++) + { + if (!match(_builder[j], arg)) + { + if (i != j) + { + _builder[i] = _builder[j]; + } + + i++; + } + } + + Clip(i); + } + public void ReverseContents() { _builder.Reverse(); diff --git a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs index 3f3c83c5355ba..7f5a193236ab7 100644 --- a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs +++ b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs @@ -98,14 +98,12 @@ protected override IList FormatBasedOnEndToken(ParsedDocument docume var endToken = root.FindToken(position); var span = GetFormattedTextSpan(root, endToken); if (span == null) - { - return SpecializedCollections.EmptyList(); - } + return []; var formatter = document.LanguageServices.GetRequiredService(); return formatter.GetFormattingResult( root, - SpecializedCollections.SingletonCollection(CommonFormattingHelpers.GetFormattingSpan(root, span.Value)), + [CommonFormattingHelpers.GetFormattingSpan(root, span.Value)], options, rules: null, cancellationToken).GetTextChanges(cancellationToken); @@ -287,7 +285,7 @@ private static IEnumerable GetOwningNodes(SyntaxNode root, int posit var token = root.FindTokenFromEnd(position); if (token.Kind() == SyntaxKind.None) { - return SpecializedCollections.EmptyEnumerable(); + return []; } return token.GetAncestors() diff --git a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs index 37655a312ed78..4986a0098536b 100644 --- a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs +++ b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs @@ -11,13 +11,15 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.AutomaticCompletion; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal partial class AutomaticLineEnderCommandHandler { #region NodeReplacementHelpers @@ -153,7 +155,7 @@ WhileStatementSyntax or ForEachStatementSyntax or ForStatementSyntax or LockStat oldNode: embeddedStatementOwner, newNode: AddBlockToEmbeddedStatementOwner(embeddedStatementOwner, formattingOptions), anchorNode: embeddedStatementOwner, - nodesToInsert: ImmutableArray.Empty.Add(statement), + nodesToInsert: [statement], formattingOptions, cancellationToken), DoStatementSyntax doStatementNode => AddBraceToDoStatement(services, root, doStatementNode, formattingOptions, statement, cancellationToken), @@ -193,7 +195,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToDoStatement oldNode: doStatementNode, newNode: AddBlockToEmbeddedStatementOwner(doStatementNode, formattingOptions), anchorNode: doStatementNode, - nodesToInsert: ImmutableArray.Empty.Add(innerStatement), + nodesToInsert: [innerStatement], formattingOptions, cancellationToken); } @@ -249,7 +251,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToIfStatement ifStatementNode, AddBlockToEmbeddedStatementOwner(ifStatementNode, formattingOptions), ifStatementNode, - ImmutableArray.Empty.Add(innerStatement), + [innerStatement], formattingOptions, cancellationToken); } @@ -317,7 +319,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToElseClause( elseClauseNode, WithBraces(elseClauseNode, formattingOptions), elseClauseNode.Parent!, - ImmutableArray.Empty.Add(innerStatement), + [innerStatement], formattingOptions, cancellationToken); } @@ -390,11 +392,11 @@ private static (SyntaxNode newNode, SyntaxNode oldNode) ModifyObjectCreationExpr // => // var l = new Bar() {}; // I am some comments var replacementContainerNode = objectCreationNodeContainer.ReplaceSyntax( - nodes: SpecializedCollections.SingletonCollection(baseObjectCreationExpressionNode), + nodes: [baseObjectCreationExpressionNode], (_, _) => objectCreationNodeWithCorrectInitializer.WithoutTrailingTrivia(), - tokens: SpecializedCollections.SingletonCollection(nextToken), + tokens: [nextToken], computeReplacementToken: (_, _) => - SyntaxFactory.Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(objectCreationNodeWithCorrectInitializer.GetTrailingTrivia()), + SemicolonToken.WithTrailingTrivia(objectCreationNodeWithCorrectInitializer.GetTrailingTrivia()), trivia: [], computeReplacementTrivia: (_, syntaxTrivia) => syntaxTrivia); return (replacementContainerNode, objectCreationNodeContainer); @@ -427,15 +429,15 @@ private static BaseObjectCreationExpressionSyntax WithArgumentListIfNeeded(BaseO // There is only 'new' keyword in the object creation expression. Treat it as an ImplicitObjectCreationExpression. // This could happen because when only type 'new', parser would think it is an ObjectCreationExpression. var newKeywordToken = baseObjectCreationExpressionNode.NewKeyword; - var newArgumentList = SyntaxFactory.ArgumentList().WithTrailingTrivia(newKeywordToken.TrailingTrivia); - return SyntaxFactory.ImplicitObjectCreationExpression(newKeywordToken.WithoutTrailingTrivia(), newArgumentList, baseObjectCreationExpressionNode.Initializer); + var newArgumentList = ArgumentList().WithTrailingTrivia(newKeywordToken.TrailingTrivia); + return ImplicitObjectCreationExpression(newKeywordToken.WithoutTrailingTrivia(), newArgumentList, baseObjectCreationExpressionNode.Initializer); } else { // Make sure the trailing trivia is passed to the argument list // like var l = new List\r\n => // var l = new List()\r\r - var newArgumentList = SyntaxFactory.ArgumentList().WithTrailingTrivia(typeNode.GetTrailingTrivia()); + var newArgumentList = ArgumentList().WithTrailingTrivia(typeNode.GetTrailingTrivia()); var newTypeNode = typeNode.WithoutTrivia(); return objectCreationExpressionNode.WithType(newTypeNode).WithArgumentList(newArgumentList); } @@ -444,8 +446,8 @@ private static BaseObjectCreationExpressionSyntax WithArgumentListIfNeeded(BaseO if (baseObjectCreationExpressionNode is ImplicitObjectCreationExpressionSyntax implicitObjectCreationExpressionNode) { var newKeywordToken = implicitObjectCreationExpressionNode.NewKeyword; - var newArgumentList = SyntaxFactory.ArgumentList().WithTrailingTrivia(newKeywordToken.TrailingTrivia); - return SyntaxFactory.ImplicitObjectCreationExpression(newKeywordToken.WithoutTrailingTrivia(), newArgumentList, baseObjectCreationExpressionNode.Initializer); + var newArgumentList = ArgumentList().WithTrailingTrivia(newKeywordToken.TrailingTrivia); + return ImplicitObjectCreationExpression(newKeywordToken.WithoutTrailingTrivia(), newArgumentList, baseObjectCreationExpressionNode.Initializer); } RoslynDebug.Assert(false, $"New derived type of {nameof(BaseObjectCreationExpressionSyntax)} is added"); @@ -834,24 +836,24 @@ private static bool ShouldRemoveBraceForEventDeclaration(EventDeclarationSyntax #region AddBrace private static AccessorListSyntax GetAccessorListNode(SyntaxFormattingOptions formattingOptions) - => SyntaxFactory.AccessorList().WithOpenBraceToken(GetOpenBrace(formattingOptions)).WithCloseBraceToken(GetCloseBrace(formattingOptions)); + => AccessorList().WithOpenBraceToken(GetOpenBrace(formattingOptions)).WithCloseBraceToken(GetCloseBrace(formattingOptions)); private static InitializerExpressionSyntax GetInitializerExpressionNode(SyntaxFormattingOptions formattingOptions) - => SyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression) + => InitializerExpression(SyntaxKind.ObjectInitializerExpression) .WithOpenBraceToken(GetOpenBrace(formattingOptions)); private static BlockSyntax GetBlockNode(SyntaxFormattingOptions formattingOptions) - => SyntaxFactory.Block().WithOpenBraceToken(GetOpenBrace(formattingOptions)).WithCloseBraceToken(GetCloseBrace(formattingOptions)); + => Block().WithOpenBraceToken(GetOpenBrace(formattingOptions)).WithCloseBraceToken(GetCloseBrace(formattingOptions)); private static SyntaxToken GetOpenBrace(SyntaxFormattingOptions formattingOptions) - => SyntaxFactory.Token( + => Token( leading: SyntaxTriviaList.Empty, kind: SyntaxKind.OpenBraceToken, trailing: [GetNewLineTrivia(formattingOptions)]) .WithAdditionalAnnotations(s_openBracePositionAnnotation); private static SyntaxToken GetCloseBrace(SyntaxFormattingOptions formattingOptions) - => SyntaxFactory.Token( + => Token( leading: SyntaxTriviaList.Empty, kind: SyntaxKind.CloseBraceToken, trailing: [GetNewLineTrivia(formattingOptions)]); @@ -859,7 +861,7 @@ private static SyntaxToken GetCloseBrace(SyntaxFormattingOptions formattingOptio private static SyntaxTrivia GetNewLineTrivia(SyntaxFormattingOptions formattingOptions) { var newLineString = formattingOptions.NewLine; - return SyntaxFactory.EndOfLine(newLineString); + return EndOfLine(newLineString); } /// @@ -885,7 +887,7 @@ private static BaseTypeDeclarationSyntax WithBracesForBaseTypeDeclaration( BaseTypeDeclarationSyntax baseTypeDeclarationNode, SyntaxFormattingOptions formattingOptions) => baseTypeDeclarationNode.WithOpenBraceToken(GetOpenBrace(formattingOptions)) - .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); + .WithCloseBraceToken(CloseBraceToken); /// /// Add an empty initializer to . @@ -903,7 +905,7 @@ private static BaseMethodDeclarationSyntax AddBlockToBaseMethodDeclaration( SyntaxFormattingOptions formattingOptions) => baseMethodDeclarationNode.WithBody(GetBlockNode(formattingOptions)) // When the method declaration with no body is parsed, it has an invisible trailing semicolon. Make sure it is removed. - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)); + .WithSemicolonToken(Token(SyntaxKind.None)); /// /// Add an empty block to . @@ -913,7 +915,7 @@ private static LocalFunctionStatementSyntax AddBlockToLocalFunctionDeclaration( SyntaxFormattingOptions formattingOptions) => localFunctionStatementNode.WithBody(GetBlockNode(formattingOptions)) // When the local method declaration with no body is parsed, it has an invisible trailing semicolon. Make sure it is removed. - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)); + .WithSemicolonToken(Token(SyntaxKind.None)); /// /// Add an empty block to . @@ -923,7 +925,7 @@ private static AccessorDeclarationSyntax AddBlockToAccessorDeclaration( SyntaxFormattingOptions formattingOptions) => accessorDeclarationNode.WithBody(GetBlockNode(formattingOptions)) // When the accessor with no body is parsed, it has an invisible trailing semicolon. Make sure it is removed. - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)); + .WithSemicolonToken(Token(SyntaxKind.None)); /// /// Add a block with to @@ -1001,25 +1003,25 @@ private static BaseObjectCreationExpressionSyntax RemoveInitializerForBaseObject /// private static FieldDeclarationSyntax ConvertPropertyDeclarationToFieldDeclaration( PropertyDeclarationSyntax propertyDeclarationNode) - => SyntaxFactory.FieldDeclaration( + => FieldDeclaration( propertyDeclarationNode.AttributeLists, propertyDeclarationNode.Modifiers, - SyntaxFactory.VariableDeclaration( + VariableDeclaration( propertyDeclarationNode.Type, - [SyntaxFactory.VariableDeclarator(propertyDeclarationNode.Identifier)]), - SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + [VariableDeclarator(propertyDeclarationNode.Identifier)]), + SemicolonToken); /// /// Convert to EventFieldDeclaration. /// private static EventFieldDeclarationSyntax ConvertEventDeclarationToEventFieldDeclaration( EventDeclarationSyntax eventDeclarationNode) - => SyntaxFactory.EventFieldDeclaration( + => EventFieldDeclaration( eventDeclarationNode.AttributeLists, eventDeclarationNode.Modifiers, - SyntaxFactory.VariableDeclaration( + VariableDeclaration( eventDeclarationNode.Type, - [SyntaxFactory.VariableDeclarator(eventDeclarationNode.Identifier)])); + [VariableDeclarator(eventDeclarationNode.Identifier)])); /// /// Remove the body of . @@ -1027,7 +1029,7 @@ private static EventFieldDeclarationSyntax ConvertEventDeclarationToEventFieldDe private static AccessorDeclarationSyntax RemoveBodyForAccessorDeclarationNode(AccessorDeclarationSyntax accessorDeclarationNode) => accessorDeclarationNode .WithBody(null).WithoutTrailingTrivia().WithSemicolonToken( - SyntaxFactory.Token(SyntaxTriviaList.Empty, SyntaxKind.SemicolonToken, SyntaxTriviaList.Empty)); + Token(SyntaxTriviaList.Empty, SyntaxKind.SemicolonToken, SyntaxTriviaList.Empty)); #endregion } diff --git a/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs b/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs index 4c0abffa1b6b8..bd67876b2110c 100644 --- a/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs +++ b/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs @@ -92,7 +92,7 @@ public Task> GetFormattingChangesAsync( var span = textSpan ?? new TextSpan(0, parsedDocument.Root.FullSpan.Length); var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(parsedDocument.Root, span); - return Task.FromResult(Formatter.GetFormattedTextChanges(parsedDocument.Root, SpecializedCollections.SingletonEnumerable(formattingSpan), document.Project.Solution.Services, options, cancellationToken).ToImmutableArray()); + return Task.FromResult(Formatter.GetFormattedTextChanges(parsedDocument.Root, [formattingSpan], document.Project.Solution.Services, options, cancellationToken).ToImmutableArray()); } public Task> GetFormattingChangesOnPasteAsync(Document document, ITextBuffer textBuffer, TextSpan textSpan, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj b/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj index d23c1fd10d441..b38a4252d8b6b 100644 --- a/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj +++ b/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj @@ -32,6 +32,7 @@ + diff --git a/src/EditorFeatures/CSharp/StringCopyPaste/KnownSourcePasteProcessor.cs b/src/EditorFeatures/CSharp/StringCopyPaste/KnownSourcePasteProcessor.cs index 27f9e58606bf2..52b8c4ba21028 100644 --- a/src/EditorFeatures/CSharp/StringCopyPaste/KnownSourcePasteProcessor.cs +++ b/src/EditorFeatures/CSharp/StringCopyPaste/KnownSourcePasteProcessor.cs @@ -221,7 +221,7 @@ private ImmutableArray DetermineTotalEditsToMakeToRawString( if (quotesToAdd != null) edits.Add(new TextChange(new TextSpan(StringExpressionBeforePasteInfo.EndDelimiterSpanWithoutSuffix.End, 0), quotesToAdd)); - return edits.ToImmutable(); + return edits.ToImmutableAndClear(); } private void UpdateExistingInterpolationBraces( diff --git a/src/EditorFeatures/CSharp/StringCopyPaste/UnknownSourcePasteProcessor.cs b/src/EditorFeatures/CSharp/StringCopyPaste/UnknownSourcePasteProcessor.cs index 2ad2bb0331962..05b75b0451d10 100644 --- a/src/EditorFeatures/CSharp/StringCopyPaste/UnknownSourcePasteProcessor.cs +++ b/src/EditorFeatures/CSharp/StringCopyPaste/UnknownSourcePasteProcessor.cs @@ -141,7 +141,7 @@ private ImmutableArray GetEditsForRawString() if (quotesToAdd != null) edits.Add(new TextChange(new TextSpan(StringExpressionBeforePasteInfo.EndDelimiterSpanWithoutSuffix.End, 0), quotesToAdd)); - return edits.ToImmutable(); + return edits.ToImmutableAndClear(); } /// diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf index 7f7b5c9f24d72..963edbae00617 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.cs.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + Generuje se událost... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf index dc4b02342fff9..2e9ea39a8f4ac 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.de.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + Ereignis wird generiert... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf index b59460b830055..703324f7f4afa 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.es.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + Generando evento... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf index 1d0de182ad5c9..733829e4f2449 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.fr.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + Génération en cours de l’événement... Merci de patienter. diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf index ffb05af54605b..83c13db82f1e3 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.it.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + Generazione dell'evento in corso... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf index 024dbb366400f..462b18ecbddf8 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ja.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + イベントを生成しています... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf index 0af667ee68fb1..eedaab1452dde 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ko.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + 이벤트를 생성하는 중... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf index 0869e65dfd49e..dfe6ecdeaf348 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pl.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + Trwa generowanie zdarzenia... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf index 71adb01c65ef2..8a84822d4a44b 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.pt-BR.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + Gerando evento... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf index 7a2e26b488f70..01c313e491388 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.ru.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + Создание события... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf index 4f4e0e1b6b51b..99de197b9a75c 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.tr.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + Olay oluşturuluyor... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf index 5d8955cc11670..385cff31b2c29 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hans.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + 正在生成事件... diff --git a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf index a5bf2b3f46e4e..db53b902e9b08 100644 --- a/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf +++ b/src/EditorFeatures/CSharp/xlf/CSharpEditorResources.zh-Hant.xlf @@ -34,7 +34,7 @@ Generating event... - Generating event... + 正在產生事件... diff --git a/src/EditorFeatures/CSharpTest/Classification/CopyPasteAndPrintingClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/CopyPasteAndPrintingClassifierTests.cs deleted file mode 100644 index d48553ed286d8..0000000000000 --- a/src/EditorFeatures/CSharpTest/Classification/CopyPasteAndPrintingClassifierTests.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.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.Text.Shared.Extensions; -using Microsoft.VisualStudio.Text.Tagging; -using Roslyn.Test.Utilities; -using Roslyn.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Classification -{ - [UseExportProvider] - [Trait(Traits.Feature, Traits.Features.Classification)] - public class CopyPasteAndPrintingClassifierTests - { - [WpfFact] - public async Task TestGetTagsOnBufferTagger() - { - // don't crash - using var workspace = EditorTestWorkspace.CreateCSharp("class C { C c; }"); - var document = workspace.Documents.First(); - - var listenerProvider = workspace.GetService(); - - var provider = new CopyPasteAndPrintingClassificationBufferTaggerProvider( - workspace.GetService(), - workspace.GetService(), - listenerProvider, - workspace.GlobalOptions); - - var tagger = provider.CreateTagger(document.GetTextBuffer())!; - using var disposable = (IDisposable)tagger; - var waiter = listenerProvider.GetWaiter(FeatureAttribute.Classification); - await waiter.ExpeditedWaitAsync(); - - var tags = tagger.GetTags(document.GetTextBuffer().CurrentSnapshot.GetSnapshotSpanCollection()); - var allTags = tagger.GetAllTags(document.GetTextBuffer().CurrentSnapshot.GetSnapshotSpanCollection(), CancellationToken.None); - - Assert.Empty(tags); - Assert.NotEmpty(allTags); - - Assert.Equal(1, allTags.Count()); - } - } -} diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index a538fb0385c45..c9a0a881e7b99 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -642,7 +642,8 @@ class C """, testHost, Namespace("System"), - Class("Obsolete")); + Class("Obsolete"), + Obsolete("C")); } [Theory, CombinatorialData] diff --git a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs index 04a4343628c41..49882a1d009c9 100644 --- a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs @@ -2,8 +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. +using System; using System.Collections.Immutable; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; @@ -17,6 +19,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Tagging; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -3079,13 +3082,10 @@ void M() { Keyword("using"), Namespace("System"), - Identifier("System"), Operators.Dot, Namespace("Text"), - Identifier("Text"), Operators.Dot, Namespace("RegularExpressions"), - Identifier("RegularExpressions"), Punctuation.Semicolon, Keyword("class"), Class("C"), @@ -3098,7 +3098,6 @@ void M() Punctuation.OpenCurly, Keyword("new"), Class("Regex"), - Identifier("Regex"), Punctuation.OpenParen, String("\""), Regex.Grouping("("), @@ -3108,13 +3107,11 @@ void M() Punctuation.CloseParen, Punctuation.Semicolon, Keyword("var"), - Keyword("var"), Local("s1"), Operators.Equals, String("\"s1\""), Punctuation.Semicolon, Keyword("var"), - Keyword("var"), Local("s2"), Operators.Equals, String("$\""), @@ -3122,12 +3119,111 @@ void M() String("\""), Punctuation.Semicolon, Keyword("var"), - Keyword("var"), Local("s3"), Operators.Equals, Verbatim("@\"s3\""), Punctuation.Semicolon, Keyword("var"), + Local("s4"), + Operators.Equals, + String("""" + """ + s4 + """ + """"), + Punctuation.Semicolon, + Punctuation.CloseCurly, + Punctuation.CloseCurly, + }, actualFormatted); + } + + [WpfFact] + public void TestCopyPasteClassifier() + { + using var workspace = EditorTestWorkspace.CreateCSharp("""" + using System.Text.RegularExpressions; + + class C + { + // class D { } + void M() + { + new Regex("(a)"); + var s1 = "s1"; + var s2 = $"s2"; + var s3 = @"s3"; + var s4 = """ + s4 + """; + } + } + """"); + var document = workspace.Documents.First(); + + var listenerProvider = workspace.ExportProvider.GetExportedValue(); + var globalOptions = workspace.ExportProvider.GetExportedValue(); + + var provider = new CopyPasteAndPrintingClassificationBufferTaggerProvider( + workspace.GetService(), + workspace.GetService(), + listenerProvider, + globalOptions); + + var buffer = document.GetTextBuffer(); + using var tagger = provider.CreateTagger(buffer); + + var allCode = buffer.CurrentSnapshot.GetText(); + var tags = tagger!.GetAllTags(new NormalizedSnapshotSpanCollection(buffer.CurrentSnapshot.GetFullSpan()), CancellationToken.None); + + var actualOrdered = tags.OrderBy((t1, t2) => t1.Span.Span.Start - t2.Span.Span.Start); + + var actualFormatted = actualOrdered.Select(a => new FormattedClassification(allCode.Substring(a.Span.Span.Start, a.Span.Span.Length), a.Tag.ClassificationType.Classification)); + + AssertEx.Equal(new[] + { + Keyword("using"), + Namespace("System"), + Operators.Dot, + Namespace("Text"), + Operators.Dot, + Namespace("RegularExpressions"), + Punctuation.Semicolon, + Keyword("class"), + Class("C"), + Punctuation.OpenCurly, + Comment("// class D { }"), + Keyword("void"), + Method("M"), + Punctuation.OpenParen, + Punctuation.CloseParen, + Punctuation.OpenCurly, + Keyword("new"), + Class("Regex"), + Punctuation.OpenParen, + String("\""), + Regex.Grouping("("), + Regex.Text("a"), + Regex.Grouping(")"), + String("\""), + Punctuation.CloseParen, + Punctuation.Semicolon, + Keyword("var"), + Local("s1"), + Operators.Equals, + String("\"s1\""), + Punctuation.Semicolon, + Keyword("var"), + Local("s2"), + Operators.Equals, + String("$\""), + String("s2"), + String("\""), + Punctuation.Semicolon, + Keyword("var"), + Local("s3"), + Operators.Equals, + Verbatim("@\"s3\""), + Punctuation.Semicolon, Keyword("var"), Local("s4"), Operators.Equals, diff --git a/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests.cs index 01047342c26bf..c17cda9948689 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests.cs @@ -6527,5 +6527,55 @@ class Class index: 0, parameters: new TestParameters(options: Option(FormattingOptions2.NewLine, configuredNewLine), testHost: testHost)); } + + [Theory] + [CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/24642")] + public async Task TestAddUsingWithMalformedGeneric(TestHost testHost) + { + await TestInRegularAndScript1Async( + """ + class Class + { + [|List MassageActions(ImmutableArray EditorTestCompositions.EditorFeaturesWpf - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)); + => EditorTestCompositions.EditorFeaturesWpf; #region Generate Class @@ -901,6 +899,54 @@ protected ExType(SerializationInfo info, StreamingContext context) : base(info, index: 2); } + [Fact] + public async Task TestGenerateClassFromThrowStatementOnModernDotNet_NoObsoleteConstructor() + { + var source = """ + class Class + { + void Method() + { + throw new [|ExType|](); + } + } + """; + + await TestInRegularAndScriptAsync($""" + + + {source} + + + """, """ + using System; + + class Class + { + void Method() + { + throw new ExType(); + } + + [Serializable] + private class ExType : Exception + { + public ExType() + { + } + + public ExType(string message) : base(message) + { + } + + public ExType(string message, Exception innerException) : base(message, innerException) + { + } + } + } + """, index: 2); + } + [Fact] public async Task TestAbsenceOfGenerateIntoInvokingTypeForBaseList() { diff --git a/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs b/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs index a5ed7ad17fe5b..5733000af73f9 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.cs @@ -535,6 +535,34 @@ class Class2 { } await TestMoveTypeToNewFileAsync(code, codeAfterMove, expectedDocumentName, destinationDocumentText, index: 1); } + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/72632")] + public async Task MoveNestedTypeToNewFile_Simple_DottedName_WithPrimaryConstructor() + { + var code = +@"internal class Outer() +{ + private class Inner[||] + { + } +}"; + + var codeAfterMove = +@"internal partial class Outer() +{ +}"; + + var expectedDocumentName = "Outer.Inner.cs"; + + var destinationDocumentText = +@"internal partial class Outer +{ + private class Inner + { + } +}"; + await TestMoveTypeToNewFileAsync(code, codeAfterMove, expectedDocumentName, destinationDocumentText, index: 1); + } + [WpfFact] public async Task MoveNestedTypeToNewFile_ParentHasOtherMembers() { diff --git a/src/EditorFeatures/CSharpTest/CodeActions/PreviewTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/PreviewTests.cs index a3e5cace26455..b446ad0f3255f 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/PreviewTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/PreviewTests.cs @@ -28,9 +28,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings public partial class PreviewTests : AbstractCSharpCodeActionTest { private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeaturesWpf - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) .AddParts( - typeof(MockDiagnosticUpdateSourceRegistrationService), typeof(MockPreviewPaneService)); private const string AddedDocumentName = "AddedDocument"; diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpConditionalBlockSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpConditionalBlockSnippetCompletionProviderTests.cs index 485546c5a1d1e..12f8aaf435f11 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpConditionalBlockSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpConditionalBlockSnippetCompletionProviderTests.cs @@ -126,7 +126,7 @@ public Program() } [WpfFact] - public async Task InsertSnippettInLocalFunctionTest() + public async Task InsertSnippetInLocalFunctionTest() { var markupBeforeCommit = """ class Program diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpDoSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpDoSnippetCompletionProviderTests.cs new file mode 100644 index 0000000000000..e1ae704c239b1 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpDoSnippetCompletionProviderTests.cs @@ -0,0 +1,709 @@ +// Licensed to the .NET Foundation under one or more 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.Tasks; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets; + +[Trait(Traits.Feature, Traits.Features.Completion)] +public class CSharpDoSnippetCompletionProviderTests : AbstractCSharpSnippetCompletionProviderTests +{ + protected override string ItemToCommit => "do"; + + [WpfFact] + public async Task InsertSnippetInMethodTest() + { + var markupBeforeCommit = """ + class Program + { + public void Method() + { + $$ + } + } + """; + + var expectedCodeAfterCommit = """ + class Program + { + public void Method() + { + do + { + $$ + } + while (true); + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact] + public async Task InsertSnippetInGlobalContextTest() + { + var markupBeforeCommit = """ + Ins$$ + """; + + var expectedCodeAfterCommit = """ + do + { + $$ + } + while (true); + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact] + public async Task NoSnippetInBlockNamespaceTest() + { + var markupBeforeCommit = """ + namespace Namespace + { + $$ + class Program + { + public async Task MethodAsync() + { + } + } + } + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact] + public async Task NoSnippetInFileScopedNamespaceTest() + { + var markupBeforeCommit = """ + namespace Namespace; + $$ + class Program + { + public async Task MethodAsync() + { + } + } + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact] + public async Task InsertSnippetInConstructorTest() + { + var markupBeforeCommit = """ + class Program + { + public Program() + { + var x = 5; + $$ + } + } + """; + + var expectedCodeAfterCommit = """ + class Program + { + public Program() + { + var x = 5; + do + { + $$ + } + while (true); + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact] + public async Task InsertSnippetInLocalFunctionTest() + { + var markupBeforeCommit = """ + class Program + { + public void Method() + { + var x = 5; + void LocalMethod() + { + $$ + } + } + } + """; + + var expectedCodeAfterCommit = """ + class Program + { + public void Method() + { + var x = 5; + void LocalMethod() + { + do + { + $$ + } + while (true); + } + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact] + public async Task InsertSnippetInAnonymousFunctionTest() + { + var markupBeforeCommit = """ + public delegate void Print(int value); + + static void Main(string[] args) + { + Print print = delegate(int val) { + $$ + }; + + } + """; + + var expectedCodeAfterCommit = """ + public delegate void Print(int value); + + static void Main(string[] args) + { + Print print = delegate(int val) { + do + { + $$ + } + while (true); + }; + + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact] + public async Task InsertSnippetInParenthesizedLambdaExpressionTest() + { + var markupBeforeCommit = """ + Func testForEquality = (x, y) => + { + $$ + return x == y; + }; + """; + + var expectedCodeAfterCommit = """ + Func testForEquality = (x, y) => + { + do + { + $$ + } + while (true); + return x == y; + }; + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact] + public async Task NoSnippetInSwitchExpression() + { + var markupBeforeCommit = """ + class Program + { + public void Method() + { + var operation = 2; + + var result = operation switch + { + $$ + 1 => "Case 1", + 2 => "Case 2", + 3 => "Case 3", + 4 => "Case 4", + }; + } + } + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact] + public async Task NoSnippetInSingleLambdaExpression() + { + var markupBeforeCommit = """ + class Program + { + public void Method() + { + Func f = x => $$; + } + } + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact] + public async Task NoSnippetInStringTest() + { + var markupBeforeCommit = """ + class Program + { + public void Method() + { + var str = "$$"; + } + } + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact] + public async Task NoSnippetInObjectInitializerTest() + { + var markupBeforeCommit = """ + class Program + { + public void Method() + { + var str = new Test($$); + } + } + + class Test + { + private string val; + + public Test(string val) + { + this.val = val; + } + } + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact] + public async Task NoSnippetInParameterListTest() + { + var markupBeforeCommit = """ + class Program + { + public void Method(int x, $$) + { + } + } + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact] + public async Task NoSnippetInRecordDeclarationTest() + { + var markupBeforeCommit = """ + public record Person + { + $$ + public string FirstName { get; init; } = default!; + public string LastName { get; init; } = default!; + }; + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact] + public async Task NoSnippetInVariableDeclarationTest() + { + var markupBeforeCommit = """ + class Program + { + public void Method() + { + var x = $$ + } + } + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact] + public async Task InsertSnippetWithInvocationBeforeAndAfterCursorTest() + { + var markupBeforeCommit = """ + class Program + { + public void Method() + { + Wr$$Blah + } + } + """; + + var expectedCodeAfterCommit = """ + class Program + { + public void Method() + { + do + { + $$ + } + while (true); + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact] + public async Task InsertSnippetWithInvocationUnderscoreBeforeAndAfterCursorTest() + { + var markupBeforeCommit = """ + class Program + { + public void Method() + { + _Wr$$Blah_ + } + } + """; + + var expectedCodeAfterCommit = """ + class Program + { + public void Method() + { + do + { + $$ + } + while (true); + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact] + public async Task InsertInlineSnippetForCorrectTypeTest() + { + var markupBeforeCommit = """ + class Program + { + void M(bool arg) + { + arg.$$ + } + } + """; + + var expectedCodeAfterCommit = """ + class Program + { + void M(bool arg) + { + do + { + $$ + } + while (arg); + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact] + public async Task NoInlineSnippetForIncorrectTypeTest() + { + var markupBeforeCommit = """ + class Program + { + void M(int arg) + { + arg.$$ + } + } + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfFact] + public async Task NoInlineSnippetWhenNotDirectlyExpressionStatementTest() + { + var markupBeforeCommit = """ + class Program + { + void M(bool arg) + { + System.Console.WriteLine(arg.$$); + } + } + """; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfTheory] + [InlineData("// comment")] + [InlineData("/* comment */")] + [InlineData("#region test")] + public async Task CorrectlyDealWithLeadingTriviaInInlineSnippetInMethodTest1(string trivia) + { + var markupBeforeCommit = $$""" + class Program + { + void M(bool arg) + { + {{trivia}} + arg.$$ + } + } + """; + + var expectedCodeAfterCommit = $$""" + class Program + { + void M(bool arg) + { + {{trivia}} + do + { + $$ + } + while (arg); + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfTheory] + [InlineData("#if true")] + [InlineData("#pragma warning disable CS0108")] + [InlineData("#nullable enable")] + public async Task CorrectlyDealWithLeadingTriviaInInlineSnippetInMethodTest2(string trivia) + { + var markupBeforeCommit = $$""" + class Program + { + void M(bool arg) + { + {{trivia}} + arg.$$ + } + } + """; + + var expectedCodeAfterCommit = $$""" + class Program + { + void M(bool arg) + { + {{trivia}} + do + { + $$ + } + while (arg); + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfTheory] + [InlineData("// comment")] + [InlineData("/* comment */")] + public async Task CorrectlyDealWithLeadingTriviaInInlineSnippetInGlobalStatementTest1(string trivia) + { + var markupBeforeCommit = $$""" + {{trivia}} + true.$$ + """; + + var expectedCodeAfterCommit = $$""" + {{trivia}} + do + { + $$ + } + while (true); + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfTheory] + [InlineData("#region test")] + [InlineData("#if true")] + [InlineData("#pragma warning disable CS0108")] + [InlineData("#nullable enable")] + public async Task CorrectlyDealWithLeadingTriviaInInlineSnippetInGlobalStatementTest2(string trivia) + { + var markupBeforeCommit = $$""" + {{trivia}} + true.$$ + """; + + var expectedCodeAfterCommit = $$""" + + {{trivia}} + do + { + $$ + } + while (true); + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/69598")] + public async Task InsertInlineSnippetWhenDottingBeforeContextualKeywordTest1() + { + var markupBeforeCommit = """ + using System.Collections.Generic; + + class C + { + void M(bool flag) + { + flag.$$ + var a = 0; + } + } + """; + + var expectedCodeAfterCommit = """ + using System.Collections.Generic; + + class C + { + void M(bool flag) + { + do + { + $$ + } + while (flag); + var a = 0; + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/69598")] + public async Task InsertInlineSnippetWhenDottingBeforeContextualKeywordTest2() + { + var markupBeforeCommit = """ + using System.Collections.Generic; + + class C + { + void M(bool flag, Task t) + { + flag.$$ + await t; + } + } + """; + + var expectedCodeAfterCommit = $$""" + using System.Collections.Generic; + + class C + { + void M(bool flag, Task t) + { + do + { + $$ + } + while (flag); + await t; + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfTheory, WorkItem("https://github.com/dotnet/roslyn/issues/69598")] + [InlineData("Task")] + [InlineData("Task")] + [InlineData("System.Threading.Tasks.Task")] + public async Task InsertInlineSnippetWhenDottingBeforeNameSyntaxTest(string nameSyntax) + { + var markupBeforeCommit = $$""" + using System.Collections.Generic; + + class C + { + void M(bool flag) + { + flag.$$ + {{nameSyntax}} t = null; + } + } + """; + + var expectedCodeAfterCommit = $$""" + using System.Collections.Generic; + + class C + { + void M(bool flag) + { + do + { + $$ + } + while (flag); + {{nameSyntax}} t = null; + } + } + """; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs index d8f8aeeb0ab5b..2a2ae5897a34c 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs @@ -12696,6 +12696,179 @@ public enum Enum { A, B, C, D } """; } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72457")] + public async Task ConstrainedGenericExtensionMethods_01() + { + var markup = """ + using System.Collections.Generic; + using System.Linq; + + namespace Extensions; + + public static class GenericExtensions + { + public static string FirstOrDefaultOnHashSet(this T s) + where T : HashSet + { + return s.FirstOrDefault(); + } + public static string FirstOrDefaultOnList(this T s) + where T : List + { + return s.FirstOrDefault(); + } + } + + class C + { + void M() + { + var list = new List(); + list.$$ + } + } + """; + + await VerifyItemExistsAsync(markup, "FirstOrDefaultOnList", displayTextSuffix: "<>"); + await VerifyItemIsAbsentAsync(markup, "FirstOrDefaultOnHashSet", displayTextSuffix: "<>"); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72457")] + public async Task ConstrainedGenericExtensionMethods_02() + { + var markup = """ + using System.Collections.Generic; + using System.Linq; + + namespace Extensions; + + public static class GenericExtensions + { + public static string FirstOrDefaultOnHashSet(this T s) + where T : HashSet + { + return s.FirstOrDefault(); + } + public static string FirstOrDefaultOnList(this T s) + where T : List + { + return s.FirstOrDefault(); + } + + public static bool HasFirstNonNullItemOnList(this T s) + where T : List + { + return s.$$ + } + } + """; + + await VerifyItemExistsAsync(markup, "FirstOrDefaultOnList", displayTextSuffix: "<>"); + await VerifyItemIsAbsentAsync(markup, "FirstOrDefaultOnHashSet", displayTextSuffix: "<>"); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72457")] + public async Task ConstrainedGenericExtensionMethods_SelfGeneric01() + { + var markup = """ + using System.Collections.Generic; + using System.Linq; + + namespace Extensions; + + public static class GenericExtensions + { + public static T FirstOrDefaultOnHashSet(this T s) + where T : HashSet + { + return s.FirstOrDefault(); + } + public static T FirstOrDefaultOnList(this T s) + where T : List + { + return s.FirstOrDefault(); + } + } + + public class ListExtension : List> + where T : List + { + public void Method() + { + this.$$ + } + } + """; + + await VerifyItemExistsAsync(markup, "FirstOrDefaultOnList", displayTextSuffix: "<>"); + await VerifyItemIsAbsentAsync(markup, "FirstOrDefaultOnHashSet", displayTextSuffix: "<>"); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72457")] + public async Task ConstrainedGenericExtensionMethods_SelfGeneric02() + { + var markup = """ + using System.Collections.Generic; + using System.Linq; + + namespace Extensions; + + public static class GenericExtensions + { + public static T FirstOrDefaultOnHashSet(this T s) + where T : HashSet + { + return s.FirstOrDefault(); + } + public static T FirstOrDefaultOnList(this T s) + where T : List + { + return s.FirstOrDefault(); + } + + public static bool HasFirstNonNullItemOnList(this T s) + where T : List + { + return s.$$ + } + } + """; + + await VerifyItemExistsAsync(markup, "FirstOrDefaultOnList", displayTextSuffix: "<>"); + await VerifyItemIsAbsentAsync(markup, "FirstOrDefaultOnHashSet", displayTextSuffix: "<>"); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72457")] + public async Task ConstrainedGenericExtensionMethods_SelfGeneric03() + { + var markup = """ + namespace Extensions; + + public interface IBinaryInteger + { + public static T AdditiveIdentity { get; } + } + + public static class GenericExtensions + { + public static T AtLeastAdditiveIdentity(this T s) + where T : IBinaryInteger + { + return T.AdditiveIdentity > s ? s : T.AdditiveIdentity; + } + + public static T Method(this T s) + where T : IBinaryInteger + { + return s.$$ + } + } + """; + + await VerifyItemExistsAsync(markup, "AtLeastAdditiveIdentity", displayTextSuffix: "<>"); + await VerifyItemExistsAsync(markup, "Method", displayTextSuffix: "<>"); + } + #region Collection expressions [Fact] diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs index 94ca88f585996..b9c2606c202f9 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs @@ -8,8 +8,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data; using Roslyn.Test.Utilities; @@ -872,7 +872,7 @@ static void Goo() [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/11490")] public async Task SeeLangwordAttributeValue() { - await VerifyItemsExistAsync(""" + var source = """ class C { /// @@ -882,7 +882,21 @@ static void Goo() { } } - """, "null", "true", "false", "await"); + """; + + foreach (var keywordKind in SyntaxFacts.GetKeywordKinds()) + { + var keywordText = SyntaxFacts.GetText(keywordKind); + + if (keywordText[0] == '_') + { + await VerifyItemIsAbsentAsync(source, keywordText); + } + else + { + await VerifyItemExistsAsync(source, keywordText, glyph: (int)Glyph.Keyword); + } + } } [Fact] diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs index 36caa60843275..86224d48058c0 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs @@ -32,9 +32,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.UserDiagnos [UseExportProvider] public class DiagnosticAnalyzerDriverTests { - private static readonly TestComposition s_compositionWithMockDiagnosticUpdateSourceRegistrationService = EditorTestCompositions.EditorFeatures - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)); + private static readonly TestComposition s_compositionWithMockDiagnosticUpdateSourceRegistrationService = EditorTestCompositions.EditorFeatures; [Fact] public async Task DiagnosticAnalyzerDriverAllInOne() diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs index bb0482d9154a7..66757d94eb47e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs @@ -40,15 +40,15 @@ public async Task TrackingService_GetLatestSpansAsync(bool scheduleInitialTracki spanProvider.GetBaseActiveStatementSpansImpl = (_, documentIds) => ImmutableArray.Create( ImmutableArray.Create( - new ActiveStatementSpan(0, span11, ActiveStatementFlags.NonLeafFrame, unmappedDocumentId: null), - new ActiveStatementSpan(1, span12, ActiveStatementFlags.LeafFrame, unmappedDocumentId: null)), + new ActiveStatementSpan(new ActiveStatementId(0), span11, ActiveStatementFlags.NonLeafFrame), + new ActiveStatementSpan(new ActiveStatementId(1), span12, ActiveStatementFlags.LeafFrame)), ImmutableArray.Empty); spanProvider.GetAdjustedActiveStatementSpansImpl = (document, _) => document.Name switch { "1.cs" => ImmutableArray.Create( - new ActiveStatementSpan(0, span21, ActiveStatementFlags.NonLeafFrame, unmappedDocumentId: null), - new ActiveStatementSpan(1, span22, ActiveStatementFlags.LeafFrame, unmappedDocumentId: null)), + new ActiveStatementSpan(new ActiveStatementId(0), span21, ActiveStatementFlags.NonLeafFrame), + new ActiveStatementSpan(new ActiveStatementId(1), span22, ActiveStatementFlags.LeafFrame)), "2.cs" => ImmutableArray.Empty, _ => throw ExceptionUtilities.Unreachable() }; diff --git a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs index 22ba9d5e5adbe..d99c8efeb7be6 100644 --- a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs +++ b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodBase.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ExtractMethod @@ -30,7 +31,7 @@ protected static async Task ExpectExtractMethodToFailAsync(string codeWithMarker ParseOptions parseOptions = null; if (features != null) { - var featuresMapped = features.Select(x => new KeyValuePair(x, string.Empty)); + var featuresMapped = features.Select(x => KeyValuePairUtil.Create(x, string.Empty)); parseOptions = new CSharpParseOptions().WithFeatures(featuresMapped); } diff --git a/src/EditorFeatures/CSharpTest/ExtractMethod/MiscTests.cs b/src/EditorFeatures/CSharpTest/ExtractMethod/MiscTests.cs index edd04c968e6a1..8135175500818 100644 --- a/src/EditorFeatures/CSharpTest/ExtractMethod/MiscTests.cs +++ b/src/EditorFeatures/CSharpTest/ExtractMethod/MiscTests.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions; using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.ExtractMethod; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -21,6 +20,8 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ExtractMethod { + using static CSharpSyntaxTokens; + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.ExtractMethod)] public class MiscTests @@ -48,7 +49,7 @@ class A var publicToken = rootWithAnnotation.DescendantTokens().First(t => t.Kind() == SyntaxKind.PublicKeyword); // replace the token with new one - var newRoot = rootWithAnnotation.ReplaceToken(publicToken, SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); + var newRoot = rootWithAnnotation.ReplaceToken(publicToken, PrivateKeyword); // restore trivia around it var rootWithTriviaRestored = result.RestoreTrivia(newRoot); @@ -93,7 +94,7 @@ class A var publicToken = rootWithAnnotation.DescendantTokens().First(t => t.Kind() == SyntaxKind.PublicKeyword); // replace the token with new one - var newRoot = rootWithAnnotation.ReplaceToken(publicToken, SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); + var newRoot = rootWithAnnotation.ReplaceToken(publicToken, PrivateKeyword); // restore trivia around it var rootWithTriviaRestored = result.RestoreTrivia(newRoot); diff --git a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs index ed4f45d17d1b9..03a2dfe0fdc1a 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs @@ -16,24 +16,17 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Formatting; using Microsoft.CodeAnalysis.CSharp.UseExpressionBody; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics.CSharp; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Editor.UnitTests; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.Simplification; -using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.UnitTests.Diagnostics; using Roslyn.Test.Utilities; -using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting @@ -633,9 +626,9 @@ private void Method() } [Theory] - [InlineData(LanguageNames.CSharp)] - [InlineData(LanguageNames.VisualBasic)] - public void VerifyAllCodeStyleFixersAreSupportedByCodeCleanup(string language) + [InlineData(LanguageNames.CSharp, 49)] + [InlineData(LanguageNames.VisualBasic, 86)] + public void VerifyAllCodeStyleFixersAreSupportedByCodeCleanup(string language, int numberOfUnsupportedDiagnosticIds) { var supportedDiagnostics = GetSupportedDiagnosticIdsForCodeCleanupService(language); @@ -646,15 +639,7 @@ public void VerifyAllCodeStyleFixersAreSupportedByCodeCleanup(string language) var ideDiagnosticIds = typeof(IDEDiagnosticIds).GetFields().Select(f => f.GetValue(f) as string).ToArray(); var unsupportedDiagnosticIds = ideDiagnosticIds.Except(supportedDiagnostics).ToArray(); - var expectedNumberOfUnsupportedDiagnosticIds = - language switch - { - LanguageNames.CSharp => 48, - LanguageNames.VisualBasic => 85, - _ => throw ExceptionUtilities.UnexpectedValue(language), - }; - - Assert.Equal(expectedNumberOfUnsupportedDiagnosticIds, unsupportedDiagnosticIds.Length); + Assert.Equal(numberOfUnsupportedDiagnosticIds, unsupportedDiagnosticIds.Length); } private const string _code = """ diff --git a/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs b/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs index d53aef0d65d69..8462f9b447d79 100644 --- a/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs @@ -39,8 +39,6 @@ public async Task ErrorTagGeneratedForErrorInSourceGeneratedDocument() private static async Task>> GetTagSpansAsync(string content) { using var workspace = EditorTestWorkspace.CreateCSharp(content, composition: SquiggleUtilities.WpfCompositionWithSolutionCrawler); - Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( - workspace.CurrentSolution.Options.WithChangedOption(new OptionKey(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag), false)))); return await GetTagSpansAsync(workspace); } @@ -50,8 +48,6 @@ private static async Task>> GetTag files: [], sourceGeneratedFiles: new[] { content }, composition: SquiggleUtilities.WpfCompositionWithSolutionCrawler); - Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( - workspace.CurrentSolution.Options.WithChangedOption(new OptionKey(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag), false)))); return await GetTagSpansAsync(workspace); } @@ -59,6 +55,6 @@ private static async Task>> GetTag private static async Task>> GetTagSpansAsync(EditorTestWorkspace workspace) { workspace.GlobalOptions.SetGlobalOption(InlineDiagnosticsOptionsStorage.EnableInlineDiagnostics, LanguageNames.CSharp, true); - return (await TestDiagnosticTagProducer.GetDiagnosticsAndErrorSpans(workspace)).Item2; + return await TestDiagnosticTagProducer.GetTagSpansAsync(workspace); } } diff --git a/src/EditorFeatures/CSharpTest/Interactive/BraceMatching/InteractiveBraceHighlightingTests.cs b/src/EditorFeatures/CSharpTest/Interactive/BraceMatching/InteractiveBraceHighlightingTests.cs index 4a68299a80d32..09876451f7782 100644 --- a/src/EditorFeatures/CSharpTest/Interactive/BraceMatching/InteractiveBraceHighlightingTests.cs +++ b/src/EditorFeatures/CSharpTest/Interactive/BraceMatching/InteractiveBraceHighlightingTests.cs @@ -45,7 +45,9 @@ private static async Task>> ProduceTagsA var context = new TaggerContext( buffer.CurrentSnapshot.GetRelatedDocumentsWithChanges().FirstOrDefault(), - buffer.CurrentSnapshot, new SnapshotPoint(buffer.CurrentSnapshot, position)); + buffer.CurrentSnapshot, + frozenPartialSemantics: false, + new SnapshotPoint(buffer.CurrentSnapshot, position)); await producer.GetTestAccessor().ProduceTagsAsync(context); return context.TagSpans; diff --git a/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj b/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj index 8cd4779bdf502..2d839832ddbbe 100644 --- a/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj +++ b/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj @@ -45,6 +45,7 @@ + diff --git a/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs b/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs index d1c1194846cec..abdf779fd6416 100644 --- a/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs +++ b/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs @@ -28,9 +28,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.MoveToNamespace public class MoveToNamespaceTests : AbstractMoveToNamespaceTests { private static readonly TestComposition s_compositionWithoutOptions = FeaturesTestCompositions.Features - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) .AddParts( - typeof(MockDiagnosticUpdateSourceRegistrationService), typeof(TestSymbolRenamedCodeActionOperationFactoryWorkspaceService)); private static readonly TestComposition s_composition = s_compositionWithoutOptions.AddParts( diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index 37f4bdade55e7..cf59f9601eb94 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -7,15 +7,14 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Editor.Test; +using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.PatternMatching; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Moq; -using Moq.Language.Flow; using Roslyn.Test.Utilities; using Xunit; @@ -23,13 +22,15 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.NavigateTo { [UseExportProvider] [Trait(Traits.Feature, Traits.Features.NavigateTo)] - public class NavigateToSearcherTests + public sealed class NavigateToSearcherTests { + private static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); + private static void SetupSearchProject( Mock searchService, string pattern, bool isFullyLoaded, - INavigateToSearchResult? result) + ImmutableArray results) { if (isFullyLoaded) { @@ -41,7 +42,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny, Task>>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -50,12 +51,12 @@ private static void SetupSearchProject( string pattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) => { - if (result != null) - onResultFound(null!, result); + if (results.Length > 0) + onResultsFound(results); }).Returns(Task.CompletedTask); searchService.Setup(ss => ss.SearchGeneratedDocumentsAsync( @@ -64,7 +65,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny, Task>>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -72,12 +73,12 @@ private static void SetupSearchProject( string pattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) => { - if (result != null) - onResultFound(null!, result); + if (results.Length > 0) + onResultsFound(results); }).Returns(Task.CompletedTask); // Followed by a generated doc search. @@ -91,7 +92,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny, Task>>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -100,12 +101,12 @@ private static void SetupSearchProject( string pattern2, IImmutableSet kinds, Document? activeDocument, - Func onResultFound2, + Func, Task> onResultsFound2, Func onProjectCompleted, CancellationToken cancellationToken) => { - if (result != null) - onResultFound2(null!, result); + if (results.Length > 0) + onResultsFound2(results); }).Returns(Task.CompletedTask); } } @@ -120,10 +121,10 @@ public async Task NotFullyLoadedOnlyMakesOneSearchProjectCallIfValueReturned() var pattern = "irrelevant"; - var result = new TestNavigateToSearchResult(workspace, new TextSpan(0, 0)); + var results = ImmutableArray.Create(new TestNavigateToSearchResult(workspace, new TextSpan(0, 0))); var searchService = new Mock(MockBehavior.Strict); - SetupSearchProject(searchService, pattern, isFullyLoaded: false, result); + SetupSearchProject(searchService, pattern, isFullyLoaded: false, results); // Simulate a host that says the solution isn't fully loaded. var hostMock = new Mock(MockBehavior.Strict); @@ -133,20 +134,19 @@ public async Task NotFullyLoadedOnlyMakesOneSearchProjectCallIfValueReturned() var callbackMock = new Mock(MockBehavior.Strict); callbackMock.Setup(c => c.ReportIncomplete()); callbackMock.Setup(c => c.ReportProgress(It.IsAny(), It.IsAny())); - callbackMock.Setup(c => c.AddItemAsync(It.IsAny(), result, It.IsAny())).Returns(Task.CompletedTask); + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())).Returns(Task.CompletedTask); // Because we returned a result when not fully loaded, we should notify the user that data was not complete. callbackMock.Setup(c => c.Done(false)); var searcher = NavigateToSearcher.Create( workspace.CurrentSolution, - AsynchronousOperationListenerProvider.NullListener, callbackMock.Object, pattern, kinds: ImmutableHashSet.Empty, hostMock.Object); - await searcher.SearchAsync(searchCurrentDocument: false, CancellationToken.None); + await searcher.SearchAsync(NavigateToSearchScope.Solution, CancellationToken.None); } [Theory, CombinatorialData] @@ -156,14 +156,14 @@ public async Task NotFullyLoadedMakesTwoSearchProjectCallIfValueNotReturned(bool var pattern = "irrelevant"; - var result = new TestNavigateToSearchResult(workspace, new TextSpan(0, 0)); + var results = ImmutableArray.Create(new TestNavigateToSearchResult(workspace, new TextSpan(0, 0))); var searchService = new Mock(MockBehavior.Strict); // First call will pass in that we're not fully loaded. If we return null, we should get // another call with the request to search the fully loaded data. - SetupSearchProject(searchService, pattern, isFullyLoaded: false, result: null); - SetupSearchProject(searchService, pattern, isFullyLoaded: true, result); + SetupSearchProject(searchService, pattern, isFullyLoaded: false, results: []); + SetupSearchProject(searchService, pattern, isFullyLoaded: true, results); // Simulate a host that says the solution isn't fully loaded. var hostMock = new Mock(MockBehavior.Strict); @@ -173,7 +173,7 @@ public async Task NotFullyLoadedMakesTwoSearchProjectCallIfValueNotReturned(bool var callbackMock = new Mock(MockBehavior.Strict); callbackMock.Setup(c => c.ReportIncomplete()); callbackMock.Setup(c => c.ReportProgress(It.IsAny(), It.IsAny())); - callbackMock.Setup(c => c.AddItemAsync(It.IsAny(), result, It.IsAny())) + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())) .Returns(Task.CompletedTask); // Because the remote host wasn't fully loaded, we still notify that our results may be incomplete. @@ -181,13 +181,12 @@ public async Task NotFullyLoadedMakesTwoSearchProjectCallIfValueNotReturned(bool var searcher = NavigateToSearcher.Create( workspace.CurrentSolution, - AsynchronousOperationListenerProvider.NullListener, callbackMock.Object, pattern, kinds: ImmutableHashSet.Empty, hostMock.Object); - await searcher.SearchAsync(searchCurrentDocument: false, CancellationToken.None); + await searcher.SearchAsync(NavigateToSearchScope.Solution, CancellationToken.None); } [Theory, CombinatorialData] @@ -202,8 +201,8 @@ public async Task NotFullyLoadedStillReportsAsNotCompleteIfRemoteHostIsStillHydr // First call will pass in that we're not fully loaded. If we return null, we should get another call with // the request to search the fully loaded data. If we don't report anything the second time, we will still // tell the user the search was complete. - SetupSearchProject(searchService, pattern, isFullyLoaded: false, result: null); - SetupSearchProject(searchService, pattern, isFullyLoaded: true, result: null); + SetupSearchProject(searchService, pattern, isFullyLoaded: false, results: []); + SetupSearchProject(searchService, pattern, isFullyLoaded: true, results: []); // Simulate a host that says the solution isn't fully loaded. var hostMock = new Mock(MockBehavior.Strict); @@ -219,13 +218,12 @@ public async Task NotFullyLoadedStillReportsAsNotCompleteIfRemoteHostIsStillHydr var searcher = NavigateToSearcher.Create( workspace.CurrentSolution, - AsynchronousOperationListenerProvider.NullListener, callbackMock.Object, pattern, kinds: ImmutableHashSet.Empty, hostMock.Object); - await searcher.SearchAsync(searchCurrentDocument: false, CancellationToken.None); + await searcher.SearchAsync(NavigateToSearchScope.Solution, CancellationToken.None); } [Fact] @@ -235,12 +233,12 @@ public async Task FullyLoadedMakesSingleSearchProjectCallIfValueNotReturned() var pattern = "irrelevant"; - var result = new TestNavigateToSearchResult(workspace, new TextSpan(0, 0)); + var results = ImmutableArray.Create(new TestNavigateToSearchResult(workspace, new TextSpan(0, 0))); var searchService = new Mock(MockBehavior.Strict); // First call will pass in that we're fully loaded. If we return null, we should not get another call. - SetupSearchProject(searchService, pattern, isFullyLoaded: true, result: null); + SetupSearchProject(searchService, pattern, isFullyLoaded: true, results: []); // Simulate a host that says the solution is fully loaded. var hostMock = new Mock(MockBehavior.Strict); @@ -249,7 +247,7 @@ public async Task FullyLoadedMakesSingleSearchProjectCallIfValueNotReturned() var callbackMock = new Mock(MockBehavior.Strict); callbackMock.Setup(c => c.ReportProgress(It.IsAny(), It.IsAny())); - callbackMock.Setup(c => c.AddItemAsync(It.IsAny(), result, It.IsAny())) + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())) .Returns(Task.CompletedTask); // Because we did a full search, we should let the user know it was totally accurate. @@ -257,13 +255,12 @@ public async Task FullyLoadedMakesSingleSearchProjectCallIfValueNotReturned() var searcher = NavigateToSearcher.Create( workspace.CurrentSolution, - AsynchronousOperationListenerProvider.NullListener, callbackMock.Object, pattern, kinds: ImmutableHashSet.Empty, hostMock.Object); - await searcher.SearchAsync(searchCurrentDocument: false, CancellationToken.None); + await searcher.SearchAsync(NavigateToSearchScope.Solution, CancellationToken.None); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1933220")] @@ -272,7 +269,7 @@ public async Task DoNotCrashWithoutSearchService() using var workspace = EditorTestWorkspace.CreateCSharp(""); var pattern = "irrelevant"; - var result = new TestNavigateToSearchResult(workspace, new TextSpan(0, 0)); + var results = ImmutableArray.Create(new TestNavigateToSearchResult(workspace, new TextSpan(0, 0))); var hostMock = new Mock(MockBehavior.Strict); hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(() => new ValueTask(true)); @@ -283,22 +280,117 @@ public async Task DoNotCrashWithoutSearchService() var callbackMock = new Mock(MockBehavior.Strict); callbackMock.Setup(c => c.ReportIncomplete()); callbackMock.Setup(c => c.ReportProgress(It.IsAny(), It.IsAny())); - callbackMock.Setup(c => c.AddItemAsync(It.IsAny(), result, It.IsAny())).Returns(Task.CompletedTask); + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())).Returns(Task.CompletedTask); callbackMock.Setup(c => c.Done(true)); var searcher = NavigateToSearcher.Create( workspace.CurrentSolution, - AsynchronousOperationListenerProvider.NullListener, callbackMock.Object, pattern, kinds: ImmutableHashSet.Empty, hostMock.Object); - await searcher.SearchAsync(searchCurrentDocument: false, CancellationToken.None); + await searcher.SearchAsync(NavigateToSearchScope.Solution, CancellationToken.None); + } + + [Fact] + public async Task ProjectScopeSearchingOnlySearchesSingleProjectForGeneratedDocuments() + { + using var workspace = EditorTestWorkspace.Create(""" + + + + public class C + { + } + + + + + public class D + { + } + + + + """, composition: FirstActiveAndVisibleComposition); + + var pattern = "irrelevant"; + var results = ImmutableArray.Create(new TestNavigateToSearchResult(workspace, new TextSpan(0, 0))); + + var hostMock = new Mock(MockBehavior.Strict); + hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(() => new ValueTask(true)); + + var searchGeneratedDocumentsAsyncCalled = false; + var searchService = new MockAdvancedNavigateToSearchService + { + OnSearchGeneratedDocumentsAsyncCalled = () => + { + Assert.False(searchGeneratedDocumentsAsyncCalled); + searchGeneratedDocumentsAsyncCalled = true; + } + }; + + // Ensure that returning null for the search service doesn't crash. + hostMock.Setup(h => h.GetNavigateToSearchService(It.IsAny())).Returns(() => searchService); + + var callbackMock = new Mock(MockBehavior.Strict); + callbackMock.Setup(c => c.ReportIncomplete()); + callbackMock.Setup(c => c.ReportProgress(It.IsAny(), It.IsAny())); + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())).Returns(Task.CompletedTask); + + callbackMock.Setup(c => c.Done(true)); + + var searcher = NavigateToSearcher.Create( + workspace.CurrentSolution, + callbackMock.Object, + pattern, + kinds: ImmutableHashSet.Empty, + hostMock.Object); + + // We're searching for a singular project, so we should only get a single call to search generated documents. + await searcher.SearchAsync(NavigateToSearchScope.Project, CancellationToken.None); + Assert.True(searchGeneratedDocumentsAsyncCalled); + } + + private sealed class MockAdvancedNavigateToSearchService : IAdvancedNavigateToSearchService + { + public IImmutableSet KindsProvided => AbstractNavigateToSearchService.AllKinds; + + public bool CanFilter => true; + + public Action? OnSearchCachedDocumentsAsyncCalled { get; set; } + public Action? OnSearchDocumentsAsyncCalled { get; set; } + public Action? OnSearchGeneratedDocumentsAsyncCalled { get; set; } + public Action? OnSearchProjectsAsyncCalled { get; set; } + + public Task SearchCachedDocumentsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) + { + OnSearchCachedDocumentsAsyncCalled?.Invoke(); + return Task.CompletedTask; + } + + public Task SearchDocumentAsync(Document document, string searchPattern, IImmutableSet kinds, Func, Task> onResultsFound, CancellationToken cancellationToken) + { + OnSearchDocumentsAsyncCalled?.Invoke(); + return Task.CompletedTask; + } + + public Task SearchGeneratedDocumentsAsync(Solution solution, ImmutableArray projects, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) + { + OnSearchGeneratedDocumentsAsyncCalled?.Invoke(); + return Task.CompletedTask; + } + + public Task SearchProjectsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) + { + OnSearchProjectsAsyncCalled?.Invoke(); + return Task.CompletedTask; + } } - private class TestNavigateToSearchResult(EditorTestWorkspace workspace, TextSpan sourceSpan) + private sealed class TestNavigateToSearchResult(EditorTestWorkspace workspace, TextSpan sourceSpan) : INavigateToSearchResult, INavigableItem { public INavigableItem.NavigableDocument Document => INavigableItem.NavigableDocument.FromDocument(workspace.CurrentSolution.Projects.Single().Documents.Single()); diff --git a/src/EditorFeatures/CSharpTest/ObsoleteSymbol/CSharpObsoleteSymbolTests.cs b/src/EditorFeatures/CSharpTest/ObsoleteSymbol/CSharpObsoleteSymbolTests.cs new file mode 100644 index 0000000000000..52499ead36c21 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/ObsoleteSymbol/CSharpObsoleteSymbolTests.cs @@ -0,0 +1,200 @@ +// Licensed to the .NET Foundation under one or more 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.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.ObsoleteSymbol; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ObsoleteSymbol; + +public class CSharpObsoleteSymbolTests : AbstractObsoleteSymbolTests +{ + protected override EditorTestWorkspace CreateWorkspace(string markup) + => EditorTestWorkspace.CreateCSharp(markup); + + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + [InlineData("interface")] + [InlineData("enum")] + public async Task TestObsoleteTypeDefinition(string keyword) + { + await TestAsync( + $$""" + [System.Obsolete] + {{keyword}} [|ObsoleteType|] + { + } + + {{keyword}} NonObsoleteType + { + } + """); + } + + [Fact] + public async Task TestObsoleteDelegateTypeDefinition() + { + await TestAsync( + """ + [System.Obsolete] + delegate void [|ObsoleteType|](); + + delegate void NonObsoleteType(); + """); + } + + [Fact] + public async Task TestDeclarationAndUseOfObsoleteAlias() + { + await TestAsync( + """ + using [|ObsoleteAlias|] = [|ObsoleteType|]; + + [System.Obsolete] + class [|ObsoleteType|]; + + /// + /// + class NonObsoleteType + { + [|ObsoleteAlias|] field = new [|ObsoleteType|](); + } + """); + } + + [Fact] + public async Task TestParametersAndReturnTypes() + { + await TestAsync( + """ + [System.Obsolete] + class [|ObsoleteType|]; + + class NonObsoleteType([|ObsoleteType|] field2) + { + [|ObsoleteType|] Method([|ObsoleteType|] arg) => [|new|](); + + System.Func<[|ObsoleteType|], [|ObsoleteType|]> field = [|ObsoleteType|] ([|ObsoleteType|] arg) => [|new|](); + } + """); + } + + [Fact] + public async Task TestImplicitType() + { + await TestAsync( + """ + [System.Obsolete] + class [|ObsoleteType|] + { + public ObsoleteType() { } + + [System.Obsolete] + public [|ObsoleteType|](int x) { } + } + + class ObsoleteCtor + { + public ObsoleteCtor() { } + + [System.Obsolete] + public [|ObsoleteCtor|](int x) { } + } + + class C + { + void Method() + { + [|var|] t1 = new [|ObsoleteType|](); + [|var|] t2 = [|new|] [|ObsoleteType|](3); + [|ObsoleteType|] t3 = [|new|](); + [|ObsoleteType|] t4 = [|new|](3); + [|var|] t5 = CreateObsoleteType(); + var t6 = nameof([|ObsoleteType|]); + + var u1 = new ObsoleteCtor(); + var u2 = [|new|] ObsoleteCtor(3); + ObsoleteCtor u3 = new(); + ObsoleteCtor u4 = [|new|](3); + var u6 = nameof(ObsoleteCtor); + + [|ObsoleteType|] CreateObsoleteType() => [|new|](); + } + } + """); + } + + [Fact] + public async Task TestExtensionMethods() + { + await TestAsync( + """ + [System.Obsolete] + static class [|ObsoleteType|] + { + public static void ObsoleteMember1(this C ignored) { } + + [System.Obsolete] + public static void [|ObsoleteMember2|](this C ignored) { } + } + + class C + { + void Method() + { + this.ObsoleteMember1(); + this.[|ObsoleteMember2|](); + [|ObsoleteType|].ObsoleteMember1(this); + [|ObsoleteType|].[|ObsoleteMember2|](this); + } + } + """); + } + + [Fact] + public async Task TestGenerics() + { + await TestAsync( + """ + [System.Obsolete] + class [|ObsoleteType|]; + + [System.Obsolete] + struct [|ObsoleteValueType|]; + + class G + { + } + + class C + { + void M() { } + + /// + /// This looks like a reference to an obsolete type, but it's actually just an identifier alias for the + /// generic type parameter 'T'. + /// + /// + void Method() + { + _ = new G<[|ObsoleteType|]>(); + _ = new G>(); + M<[|ObsoleteType|]>(); + M>(); + M>>(); + + // Mark 'var' as obsolete even when it points to Nullable where T is obsolete + [|var|] nullableValue = CreateNullableValueType(); + + [|ObsoleteValueType|]? CreateNullableValueType() => new [|ObsoleteValueType|](); + } + } + """); + } +} diff --git a/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests.cs b/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests.cs index eb6e97d8a621b..8b61883e334bd 100644 --- a/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests.cs +++ b/src/EditorFeatures/CSharpTest2/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests.cs @@ -344,7 +344,7 @@ private static void CheckCharacters(VirtualCharSequence virtualChars, ref int po private static string And(params string[] regexes) { - var conj = $"({regexes[regexes.Length - 1]})"; + var conj = $"({regexes[^1]})"; for (var i = regexes.Length - 2; i >= 0; i--) conj = $"(?({regexes[i]}){conj}|[0-[0]])"; diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs index cc300bf7dd65f..05abad2c2c64d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs @@ -631,5 +631,46 @@ public class C where T : List<(int, string)> $$ } """); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72821")] + public async Task TestNotAfterLocalFunction() + { + await VerifyAbsenceAsync( + """ + class C + { + void M() + { + void Inner() $$ + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72821")] + public async Task TestAfterGenericLocalFunction() + { + await VerifyKeywordAsync( + """ + class C + { + void M() + { + void Inner() $$ + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72821")] + public async Task TestAfterFirstValidConstraintInGenericLocalFunction() + { + await VerifyKeywordAsync( + """ + class C + { + void M() + { + void Inner() + where T1 : C + $$ + """); + } } } diff --git a/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs b/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs index 9775e704ed1cf..654802b6f5c00 100644 --- a/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs +++ b/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs @@ -146,6 +146,25 @@ public ReassignedVariableFormatDefinition() } #endregion + #region Obsolete Symobl + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = ClassificationTypeNames.ObsoleteSymbol)] + [Name(ClassificationTypeNames.ObsoleteSymbol)] + [Order(After = Priority.High)] + [UserVisible(false)] + [ExcludeFromCodeCoverage] + private class ObsoleteSymbolFormatDefinition : ClassificationFormatDefinition + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ObsoleteSymbolFormatDefinition() + { + this.DisplayName = EditorFeaturesResources.Obsolete_symbol; + this.TextDecorations = System.Windows.TextDecorations.Strikethrough; + } + } + #endregion + #region Symbol - Static [Export(typeof(EditorFormatDefinition))] [ClassificationType(ClassificationTypeNames = ClassificationTypeNames.StaticSymbol)] diff --git a/src/EditorFeatures/Core.Wpf/EditorFeaturesWpfResources.resx b/src/EditorFeatures/Core.Wpf/EditorFeaturesWpfResources.resx index c83b28818abf3..a730e78d9432a 100644 --- a/src/EditorFeatures/Core.Wpf/EditorFeaturesWpfResources.resx +++ b/src/EditorFeatures/Core.Wpf/EditorFeaturesWpfResources.resx @@ -156,4 +156,10 @@ Get AI-powered rename suggestions (Ctrl+Space) + + Toggle rename suggestions (Ctrl+Space) + + + Generating suggestions... + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/IWpfDifferenceViewerExtensions.cs b/src/EditorFeatures/Core.Wpf/IWpfDifferenceViewerExtensions.cs index 44ffbdffe8f5c..d00224da10320 100644 --- a/src/EditorFeatures/Core.Wpf/IWpfDifferenceViewerExtensions.cs +++ b/src/EditorFeatures/Core.Wpf/IWpfDifferenceViewerExtensions.cs @@ -19,24 +19,18 @@ namespace Microsoft.CodeAnalysis.Editor.Shared.Extensions { internal static class IWpfDifferenceViewerExtensions { - private class SizeToFitHelper : ForegroundThreadAffinitizedObject + private sealed class SizeToFitHelper(IThreadingContext threadingContext, IWpfDifferenceViewer diffViewer, double minWidth) { - private readonly IWpfDifferenceViewer _diffViewer; - private readonly double _minWidth; + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly IWpfDifferenceViewer _diffViewer = diffViewer; + private readonly double _minWidth = minWidth; private double _width; private double _height; - public SizeToFitHelper(IThreadingContext threadingContext, IWpfDifferenceViewer diffViewer, double minWidth) - : base(threadingContext) - { - _diffViewer = diffViewer; - _minWidth = minWidth; - } - public async Task SizeToFitAsync(CancellationToken cancellationToken) { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) await CalculateSizeAsync(cancellationToken); @@ -85,7 +79,7 @@ void HandleSnapshotDifferenceChanged(object sender, SnapshotDifferenceChangeEven private async Task CalculateSizeAsync(CancellationToken cancellationToken) { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); IWpfTextView textView; ITextSnapshot snapshot; diff --git a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs index a0d1d1a348a82..7f6faedcac22c 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs @@ -143,7 +143,7 @@ private async Task ProduceTagsAsync( // and hence only report them for 'DiagnosticKind.AnalyzerSemantic'. if (_diagnosticKind == DiagnosticKind.AnalyzerSemantic) { - var copilotDiagnostics = await document.GetCachedCopilotDiagnosticsAsync(cancellationToken).ConfigureAwait(false); + var copilotDiagnostics = await document.GetCachedCopilotDiagnosticsAsync(requestedSpan.Span.ToTextSpan(), cancellationToken).ConfigureAwait(false); diagnostics = diagnostics.AddRange(copilotDiagnostics); } diff --git a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsAdornmentManagerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsAdornmentManagerProvider.cs index 14c05cc22f1bf..cf5cde1a3f9e3 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsAdornmentManagerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsAdornmentManagerProvider.cs @@ -33,9 +33,9 @@ internal class InlineDiagnosticsAdornmentManagerProvider : AbstractAdornmentMana [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. public InlineDiagnosticsAdornmentManagerProvider( -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. IThreadingContext threadingContext, IViewTagAggregatorFactoryService tagAggregatorFactoryService, IClassificationFormatMapService classificationFormatMapService, diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml index cb01f2428b6ef..1809edb0025e0 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml @@ -7,6 +7,7 @@ xmlns:rename="clr-namespace:Microsoft.CodeAnalysis.Editor.Implementation.InlineRename" xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging" xmlns:imagecatalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog" + xmlns:platformimaging="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Imaging" xmlns:utils="clr-namespace:Microsoft.CodeAnalysis.Utilities" xmlns:vsui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0" mc:Ignorable="d" @@ -32,7 +33,8 @@ Background="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowBackgroundBrushKey}}" BorderBrush="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowBorderBrushKey}}" BorderThickness="1" - x:Name="Outline"> + x:Name="Outline" + platformimaging:ImageThemingUtilities.ImageBackgroundColor="{Binding Path=Background, RelativeSource={RelativeSource Self}, Converter={StaticResource BrushToColorConverter}}"> @@ -50,7 +52,10 @@ x:Name="ToggleExpandButton" Grid.Column="1" Click="ToggleExpand" - Background="Transparent"> + VerticalAlignment="Top" + Margin="0 2 0 0" + Background="Transparent" + platformimaging:ImageThemingUtilities.ImageBackgroundColor="{Binding Path=Background, ElementName=Outline, Converter={StaticResource BrushToColorConverter}}" > @@ -60,52 +96,61 @@ - + + - - - - - - - + + + + - - - - - - - - - - - - + + + + + + + + + + - - + + + + + + + + + + + + + + + + + - - + - + - - - - - + @@ -214,4 +311,14 @@ VirtualizationMode="{Binding RelativeSource={RelativeSource TemplatedParent},Path=(VirtualizingStackPanel.VirtualizationMode)}" /> + + + diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs index 81482f51b862c..b1ce11fbb270d 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs @@ -102,6 +102,22 @@ public override void OnApplyTemplate() _dropDownPopup = (Popup)GetTemplateChild(DropDownPopup)!; } + private void GetSuggestionsButtonClick(object sender, RoutedEventArgs e) + { + if (_smartRenameViewModel.IsUsingResultPanel) + { + _smartRenameViewModel.IsSuggestionsPanelCollapsed = !_smartRenameViewModel.IsSuggestionsPanelCollapsed; + if (_smartRenameViewModel.IsSuggestionsPanelExpanded) + { + _smartRenameViewModel.GetSuggestionsCommand.Execute(null); + } + } + else + { + _smartRenameViewModel.GetSuggestionsCommand.Execute(null); + } + } + private void ComboBox_Unloaded(object sender, RoutedEventArgs e) { _smartRenameViewModel.SuggestedNames.CollectionChanged -= SuggestedNames_CollectionChanged; @@ -120,6 +136,10 @@ private void ComboBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEvent private void ComboBox_PreviewKeyUp(object sender, KeyEventArgs e) { + if (!_smartRenameViewModel.IsUsingDropdown) + { + return; + } if ((e.Key is Key.Up or Key.Down) && Items.Count > 0) { Assumes.NotNull(_dropDownPopup); @@ -144,6 +164,11 @@ private void ItemsPresenter_PreviewMouseUp(object sender, MouseButtonEventArgs e private void InnerTextBox_GotFocus(object sender, RoutedEventArgs e) { + e.Handled = true; // Prevent selecting all the text + if (!_smartRenameViewModel.IsUsingDropdown) + { + return; + } if (Items.Count > 0) { Assumes.NotNull(_dropDownPopup); @@ -160,7 +185,8 @@ private void InnerTextBox_LostFocus(object sender, RoutedEventArgs e) private void InnerTextBox_PreviewKeyDown(object sender, KeyEventArgs e) { Assumes.NotNull(_dropDownPopup); - if ((e.Key is Key.Escape or Key.Space or Key.Enter) && _dropDownPopup.IsOpen) + if ((e.Key is Key.Escape or Key.Space or Key.Enter) + && _dropDownPopup.IsOpen) // Handle these keystrokes when dropdown is present { _dropDownPopup.IsOpen = false; SelectAllText(); diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 4f688e9f2ee4a..146fd082e8d13 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -5,24 +5,28 @@ using System; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows.Input; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; +using Microsoft.CodeAnalysis.Editor.InlineRename; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.EditorFeatures.Lightup; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.PlatformUI; namespace Microsoft.CodeAnalysis.InlineRename.UI.SmartRename; -internal sealed class SmartRenameViewModel : INotifyPropertyChanged, IDisposable +internal sealed partial class SmartRenameViewModel : INotifyPropertyChanged, IDisposable { #pragma warning disable CS0618 // Editor team use Obsolete attribute to mark potential changing API private readonly ISmartRenameSessionWrapper _smartRenameSession; #pragma warning restore CS0618 + private readonly IGlobalOptionService _globalOptionService; private readonly IThreadingContext _threadingContext; private readonly IAsynchronousOperationListenerProvider _listenerProvider; private readonly CancellationTokenSource _cancellationTokenSource = new(); @@ -44,6 +48,8 @@ internal sealed class SmartRenameViewModel : INotifyPropertyChanged, IDisposable public string StatusMessage => _smartRenameSession.StatusMessage; public bool StatusMessageVisibility => _smartRenameSession.StatusMessageVisibility; + public bool IsUsingResultPanel { get; set; } + public bool IsUsingDropdown { get; set; } private string? _selectedSuggestedName; @@ -64,11 +70,41 @@ public string? SelectedSuggestedName } } - public static string GetSuggestionsTooltip => EditorFeaturesWpfResources.Get_AI_suggestions; + public bool IsSuggestionsPanelCollapsed + { + get => IsUsingDropdown || _globalOptionService.GetOption(InlineRenameUIOptionsStorage.CollapseSuggestionsPanel); + set + { + if (value != IsSuggestionsPanelCollapsed) + { + _globalOptionService.SetGlobalOption(InlineRenameUIOptionsStorage.CollapseSuggestionsPanel, value); + NotifyPropertyChanged(nameof(IsSuggestionsPanelCollapsed)); + NotifyPropertyChanged(nameof(IsSuggestionsPanelExpanded)); + } + } + } + + public bool IsSuggestionsPanelExpanded + { + get => IsUsingResultPanel && !IsSuggestionsPanelCollapsed; + } + + public string GetSuggestionsTooltip + => IsUsingDropdown + ? EditorFeaturesWpfResources.Get_AI_suggestions + : EditorFeaturesWpfResources.Toggle_AI_suggestions; + + public string SubmitTextOverride + => IsUsingDropdown + ? EditorFeaturesWpfResources.Enter_to_rename_shift_enter_to_preview_ctrl_space_for_ai_suggestion + : EditorFeaturesWpfResources.Enter_to_rename_shift_enter_to_preview; + + public static string GeneratingSuggestionsLabel => EditorFeaturesWpfResources.Generating_suggestions; public ICommand GetSuggestionsCommand { get; } public SmartRenameViewModel( + IGlobalOptionService globalOptionService, IThreadingContext threadingContext, IAsynchronousOperationListenerProvider listenerProvider, #pragma warning disable CS0618 // Editor team use Obsolete attribute to mark potential changing API @@ -76,6 +112,7 @@ public SmartRenameViewModel( #pragma warning restore CS0618, RenameFlyoutViewModel baseViewModel) { + _globalOptionService = globalOptionService; _threadingContext = threadingContext; _listenerProvider = listenerProvider; _smartRenameSession = smartRenameSession; @@ -85,15 +122,34 @@ public SmartRenameViewModel( this.BaseViewModel.IdentifierText = baseViewModel.IdentifierText; GetSuggestionsCommand = new DelegateCommand(OnGetSuggestionsCommandExecute, null, threadingContext.JoinableTaskFactory); + + var getSuggestionsAutomatically = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsAutomatically); + IsUsingResultPanel = getSuggestionsAutomatically; + IsUsingDropdown = !IsUsingResultPanel; + SetupTelemetry(); + if (IsUsingResultPanel && IsSuggestionsPanelExpanded) + { + OnGetSuggestionsCommandExecute(); + } } private void OnGetSuggestionsCommandExecute() { _threadingContext.ThrowIfNotOnUIThread(); + if (IsUsingResultPanel && SuggestedNames.Count > 0) + { + // Don't get suggestions again in the automatic scenario + return; + } if (_getSuggestionsTask.Status is TaskStatus.RanToCompletion or TaskStatus.Faulted or TaskStatus.Canceled) { var listener = _listenerProvider.GetListener(FeatureAttribute.SmartRename); var listenerToken = listener.BeginAsyncOperation(nameof(_smartRenameSession.GetSuggestionsAsync)); + if (IsUsingDropdown && _suggestionsDropdownTelemetry is not null) + { + _suggestionsDropdownTelemetry.DropdownButtonClickTimes += 1; + } + _getSuggestionsTask = _smartRenameSession.GetSuggestionsAsync(_cancellationTokenSource.Token).CompletesAsyncOperation(listenerToken); } } @@ -107,8 +163,14 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) var textInputBackup = BaseViewModel.IdentifierText; SuggestedNames.Clear(); + var count = 0; foreach (var name in _smartRenameSession.SuggestedNames) { + if (++count > 3 && IsUsingResultPanel) + { + // Set limit of 3 results when using the result panel + break; + } SuggestedNames.Add(name); } @@ -144,12 +206,14 @@ public void Cancel() _cancellationTokenSource.Cancel(); // It's needed by editor-side telemetry. _smartRenameSession.OnCancel(); + PostTelemetry(isCommit: false); } public void Commit(string finalIdentifierName) { // It's needed by editor-side telemetry. _smartRenameSession.OnSuccess(finalIdentifierName); + PostTelemetry(isCommit: true); } public void Dispose() @@ -158,4 +222,7 @@ public void Dispose() _smartRenameSession.Dispose(); _cancellationTokenSource.Dispose(); } + + private void NotifyPropertyChanged([CallerMemberName] string? name = null) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs new file mode 100644 index 0000000000000..107ca90395dd3 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more 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.InlineRename; +using Microsoft.CodeAnalysis.EditorFeatures.Lightup; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Telemetry; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.InlineRename.UI.SmartRename +{ + internal partial class SmartRenameViewModel + { + private SuggestionsPanelTelemetry? _suggestionsPanelTelemetry; + private SuggestionsDropdownTelemetry? _suggestionsDropdownTelemetry; + + private sealed class SuggestionsPanelTelemetry + { + public bool CollapseSuggestionsPanelWhenRenameStarts { get; set; } + } + + private sealed class SuggestionsDropdownTelemetry + { + public int DropdownButtonClickTimes { get; set; } + } + + private void SetupTelemetry() + { + var getSuggestionsAutomatically = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.GetSuggestionsAutomatically); + if (getSuggestionsAutomatically) + { + _suggestionsPanelTelemetry = new SuggestionsPanelTelemetry + { + CollapseSuggestionsPanelWhenRenameStarts = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.CollapseSuggestionsPanel) + }; + } + else + { + _suggestionsDropdownTelemetry = new SuggestionsDropdownTelemetry(); + } + } + + private void PostTelemetry(bool isCommit) + { + if (_suggestionsPanelTelemetry is not null) + { + RoslynDebug.Assert(_suggestionsDropdownTelemetry is null); + TelemetryLogging.Log(FunctionId.Copilot_Rename, KeyValueLogMessage.Create(m => + { + m[nameof(isCommit)] = isCommit; + m["UseSuggestionsPanel"] = true; + m[nameof(SuggestionsPanelTelemetry.CollapseSuggestionsPanelWhenRenameStarts)] = _suggestionsPanelTelemetry.CollapseSuggestionsPanelWhenRenameStarts; + m["CollapseSuggestionsPanelWhenRenameEnds"] = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.CollapseSuggestionsPanel); + m["smartRenameSessionInProgress"] = _smartRenameSession.IsInProgress; + })); + } + else + { + RoslynDebug.Assert(_suggestionsDropdownTelemetry is not null); + TelemetryLogging.Log(FunctionId.Copilot_Rename, KeyValueLogMessage.Create(m => + { + m[nameof(isCommit)] = isCommit; + m["UseDropDown"] = true; + m[nameof(SuggestionsDropdownTelemetry.DropdownButtonClickTimes)] = _suggestionsDropdownTelemetry.DropdownButtonClickTimes; + m["smartRenameSessionInProgress"] = _smartRenameSession.IsInProgress; + })); + } + } + } +} diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InertClassifierProvider.InertClassifier.cs b/src/EditorFeatures/Core.Wpf/Interactive/InertClassifierProvider.InertClassifier.cs index b3a5954187080..ae44543f1d20b 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InertClassifierProvider.InertClassifier.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/InertClassifierProvider.InertClassifier.cs @@ -35,7 +35,7 @@ public IList GetClassificationSpans(SnapshotSpan span) return classifications.Where(c => c.Span.IntersectsWith(span)).ToList(); } - return SpecializedCollections.EmptyList(); + return []; } } } diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveWindowResetCommand.cs b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveWindowResetCommand.cs index 69825d6096027..db725791fe4e8 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveWindowResetCommand.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveWindowResetCommand.cs @@ -51,7 +51,7 @@ public IEnumerable DetailedDescription => null; public IEnumerable Names - => SpecializedCollections.SingletonEnumerable(CommandName); + => [CommandName]; public string CommandLine => "[" + NoConfigParameterName + "] [" + PlatformNames + "]"; @@ -60,8 +60,8 @@ public IEnumerable> ParametersDescription { get { - yield return new KeyValuePair(NoConfigParameterName, EditorFeaturesWpfResources.Reset_to_a_clean_environment_only_mscorlib_referenced_do_not_run_initialization_script); - yield return new KeyValuePair(PlatformNames, EditorFeaturesWpfResources.Interactive_host_process_platform); + yield return KeyValuePairUtil.Create(NoConfigParameterName, EditorFeaturesWpfResources.Reset_to_a_clean_environment_only_mscorlib_referenced_do_not_run_initialization_script); + yield return KeyValuePairUtil.Create(PlatformNames, EditorFeaturesWpfResources.Interactive_host_process_platform); } } diff --git a/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbol.cs b/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbol.cs index 25df413e8b7e0..5851e0d25fb14 100644 --- a/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbol.cs +++ b/src/EditorFeatures/Core.Wpf/NavigableSymbols/NavigableSymbolService.NavigableSymbol.cs @@ -42,7 +42,7 @@ public NavigableSymbol( public SnapshotSpan SymbolSpan { get; } public IEnumerable Relationships - => SpecializedCollections.SingletonEnumerable(PredefinedNavigableRelationships.Definition); + => [PredefinedNavigableRelationships.Definition]; public void Navigate(INavigableRelationship relationship) { diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs index 2cf7dc85588cd..603c88e14b510 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemDisplay.cs @@ -144,6 +144,6 @@ public IReadOnlyList GetNameMatchRuns(string searchValue) => _searchResult.NameMatchSpans.NullToEmpty().SelectAsArray(ts => ts.ToSpan()); public IReadOnlyList GetAdditionalInformationMatchRuns(string searchValue) - => SpecializedCollections.EmptyReadOnlyList(); + => []; } } diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs index 7c1ff6aa7b44d..cb42cc3076b4f 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs @@ -3,10 +3,12 @@ // 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.ErrorReporting; using Microsoft.CodeAnalysis.NavigateTo; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Language.NavigateTo.Interfaces; using Microsoft.VisualStudio.Text.PatternMatching; @@ -18,11 +20,13 @@ internal partial class NavigateToItemProvider { private class NavigateToItemProviderCallback : INavigateToSearchCallback { + private readonly Solution _solution; private readonly INavigateToItemDisplayFactory _displayFactory; private readonly INavigateToCallback _callback; - public NavigateToItemProviderCallback(INavigateToItemDisplayFactory displayFactory, INavigateToCallback callback) + public NavigateToItemProviderCallback(Solution solution, INavigateToItemDisplayFactory displayFactory, INavigateToCallback callback) { + _solution = solution; _displayFactory = displayFactory; _callback = callback; } @@ -39,9 +43,41 @@ public void Done(bool isFullyLoaded) } } - public Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - ReportMatchResult(project, result); + foreach (var result in results) + { + var matchedSpans = result.NameMatchSpans.SelectAsArray(t => t.ToSpan()); + + var patternMatch = new PatternMatch( + GetPatternMatchKind(result.MatchKind), + punctuationStripped: false, + result.IsCaseSensitive, + matchedSpans); + + var project = _solution.GetRequiredProject(result.NavigableItem.Document.Project.Id); + var navigateToItem = new NavigateToItem( + result.Name, + result.Kind, + GetNavigateToLanguage(project.Language), + result.SecondarySort, + result, + patternMatch, + _displayFactory); + + try + { + _callback.AddItem(navigateToItem); + } + catch (InvalidOperationException ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) + { + // Mitigation for race condition in platform https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1534364 + // + // Catch this so that don't tear down OOP, but still report the exception so that we ensure this issue + // gets attention and is fixed. + } + } + return Task.CompletedTask; } @@ -54,38 +90,6 @@ public void ReportIncomplete() { } - private void ReportMatchResult(Project project, INavigateToSearchResult result) - { - var matchedSpans = result.NameMatchSpans.SelectAsArray(t => t.ToSpan()); - - var patternMatch = new PatternMatch( - GetPatternMatchKind(result.MatchKind), - punctuationStripped: false, - result.IsCaseSensitive, - matchedSpans); - - var navigateToItem = new NavigateToItem( - result.Name, - result.Kind, - GetNavigateToLanguage(project.Language), - result.SecondarySort, - result, - patternMatch, - _displayFactory); - - try - { - _callback.AddItem(navigateToItem); - } - catch (InvalidOperationException ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) - { - // Mitigation for race condition in platform https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1534364 - // - // Catch this so that don't tear down OOP, but still report the exception so that we ensure this issue - // gets attention and is fixed. - } - } - private static PatternMatchKind GetPatternMatchKind(NavigateToMatchKind matchKind) => matchKind switch { diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs index b68369c537a42..693198634baee 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs @@ -6,9 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -16,111 +14,114 @@ using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigateTo +namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigateTo; + +internal sealed partial class NavigateToItemProvider : INavigateToItemProvider2 { - internal partial class NavigateToItemProvider : INavigateToItemProvider2 + private readonly Workspace _workspace; + private readonly IAsynchronousOperationListener _asyncListener; + private readonly INavigateToItemDisplayFactory _displayFactory; + private readonly IThreadingContext _threadingContext; + + private CancellationTokenSource _cancellationTokenSource = new(); + + public NavigateToItemProvider( + Workspace workspace, + IThreadingContext threadingContext, + IUIThreadOperationExecutor threadOperationExecutor, + IAsynchronousOperationListener asyncListener) { - private readonly Workspace _workspace; - private readonly IAsynchronousOperationListener _asyncListener; - private readonly INavigateToItemDisplayFactory _displayFactory; - private readonly IThreadingContext _threadingContext; - - private CancellationTokenSource _cancellationTokenSource = new(); - - public NavigateToItemProvider( - Workspace workspace, - IThreadingContext threadingContext, - IUIThreadOperationExecutor threadOperationExecutor, - IAsynchronousOperationListener asyncListener) - { - Contract.ThrowIfNull(workspace); - Contract.ThrowIfNull(asyncListener); - - _workspace = workspace; - _asyncListener = asyncListener; - _displayFactory = new NavigateToItemDisplayFactory( - threadingContext, threadOperationExecutor, asyncListener); - _threadingContext = threadingContext; - } + Contract.ThrowIfNull(workspace); + Contract.ThrowIfNull(asyncListener); + + _workspace = workspace; + _asyncListener = asyncListener; + _displayFactory = new NavigateToItemDisplayFactory( + threadingContext, threadOperationExecutor, asyncListener); + _threadingContext = threadingContext; + } - ISet INavigateToItemProvider2.KindsProvided => KindsProvided; + ISet INavigateToItemProvider2.KindsProvided => KindsProvided; - public ImmutableHashSet KindsProvided - => NavigateToUtilities.GetKindsProvided(_workspace.CurrentSolution); + public ImmutableHashSet KindsProvided + => NavigateToUtilities.GetKindsProvided(_workspace.CurrentSolution); - public bool CanFilter + public bool CanFilter + { + get { - get + foreach (var project in _workspace.CurrentSolution.Projects) { - foreach (var project in _workspace.CurrentSolution.Projects) + var navigateToSearchService = project.GetLanguageService(); + if (navigateToSearchService is null) { - var navigateToSearchService = project.GetLanguageService(); - if (navigateToSearchService is null) - { - // If we reach here, it means the current project does not support Navigate To, which is - // functionally equivalent to supporting filtering. - continue; - } - - if (!navigateToSearchService.CanFilter) - { - return false; - } + // If we reach here, it means the current project does not support Navigate To, which is + // functionally equivalent to supporting filtering. + continue; } - // All projects either support filtering or do not support Navigate To at all - return true; + if (!navigateToSearchService.CanFilter) + { + return false; + } } - } - public void StopSearch() - { - _cancellationTokenSource.Cancel(); - _cancellationTokenSource = new CancellationTokenSource(); + // All projects either support filtering or do not support Navigate To at all + return true; } + } - public void Dispose() - { - this.StopSearch(); - (_displayFactory as IDisposable)?.Dispose(); - } + public void StopSearch() + { + _cancellationTokenSource.Cancel(); + _cancellationTokenSource = new CancellationTokenSource(); + } - public void StartSearch(INavigateToCallback callback, string searchValue) - => StartSearch(callback, searchValue, KindsProvided); + public void Dispose() + { + this.StopSearch(); + (_displayFactory as IDisposable)?.Dispose(); + } - public void StartSearch(INavigateToCallback callback, string searchValue, INavigateToFilterParameters filter) - => StartSearch(callback, searchValue, filter.Kinds.ToImmutableHashSet(StringComparer.Ordinal)); + public void StartSearch(INavigateToCallback callback, string searchValue) + => StartSearch(callback, searchValue, KindsProvided); - private void StartSearch(INavigateToCallback callback, string searchValue, IImmutableSet kinds) - { - this.StopSearch(); + public void StartSearch(INavigateToCallback callback, string searchValue, INavigateToFilterParameters filter) + => StartSearch(callback, searchValue, filter.Kinds.ToImmutableHashSet(StringComparer.Ordinal)); - if (string.IsNullOrWhiteSpace(searchValue)) - { - callback.Done(); - return; - } + private void StartSearch(INavigateToCallback callback, string searchValue, IImmutableSet kinds) + { + this.StopSearch(); - if (kinds == null || kinds.Count == 0) - { - kinds = KindsProvided; - } + if (string.IsNullOrWhiteSpace(searchValue)) + { + callback.Done(); + return; + } - var searchCurrentDocument = (callback.Options as INavigateToOptions2)?.SearchCurrentDocument ?? false; - - var roslynCallback = new NavigateToItemProviderCallback(_displayFactory, callback); - var searcher = NavigateToSearcher.Create( - _workspace.CurrentSolution, - _asyncListener, - roslynCallback, - searchValue, - kinds, - _threadingContext.DisposalToken); - - var asyncToken = _asyncListener.BeginAsyncOperation(nameof(StartSearch)); - _ = searcher.SearchAsync(searchCurrentDocument, _cancellationTokenSource.Token) - .CompletesAsyncOperation(asyncToken) - .ReportNonFatalErrorUnlessCancelledAsync(_cancellationTokenSource.Token); + if (kinds == null || kinds.Count == 0) + { + kinds = KindsProvided; } + + var searchCurrentDocument = (callback.Options as INavigateToOptions2)?.SearchCurrentDocument ?? false; + var searchScope = searchCurrentDocument + ? NavigateToSearchScope.Document + : NavigateToSearchScope.Solution; + + var solution = _workspace.CurrentSolution; + var roslynCallback = new NavigateToItemProviderCallback(solution, _displayFactory, callback); + var searcher = NavigateToSearcher.Create( + solution, + _asyncListener, + roslynCallback, + searchValue, + kinds, + _threadingContext.DisposalToken); + + var asyncToken = _asyncListener.BeginAsyncOperation(nameof(StartSearch)); + _ = searcher.SearchAsync(searchScope, _cancellationTokenSource.Token) + .CompletesAsyncOperation(asyncToken) + .ReportNonFatalErrorUnlessCancelledAsync(_cancellationTokenSource.Token); } } diff --git a/src/EditorFeatures/Core.Wpf/Peek/DefinitionPeekableItem.cs b/src/EditorFeatures/Core.Wpf/Peek/DefinitionPeekableItem.cs index 229200804567e..c53ede7ead51a 100644 --- a/src/EditorFeatures/Core.Wpf/Peek/DefinitionPeekableItem.cs +++ b/src/EditorFeatures/Core.Wpf/Peek/DefinitionPeekableItem.cs @@ -41,7 +41,7 @@ public DefinitionPeekableItem( } public override IEnumerable Relationships - => SpecializedCollections.SingletonEnumerable(PredefinedPeekRelationships.Definitions); + => [PredefinedPeekRelationships.Definitions]; public override IPeekResultSource GetOrCreateResultSource(string relationshipName) => new ResultSource(this); diff --git a/src/EditorFeatures/Core.Wpf/Peek/ExternalFilePeekableItem.cs b/src/EditorFeatures/Core.Wpf/Peek/ExternalFilePeekableItem.cs index b05a5ee91ad62..5ea823535e7f0 100644 --- a/src/EditorFeatures/Core.Wpf/Peek/ExternalFilePeekableItem.cs +++ b/src/EditorFeatures/Core.Wpf/Peek/ExternalFilePeekableItem.cs @@ -27,9 +27,7 @@ public ExternalFilePeekableItem( } public override IEnumerable Relationships - { - get { return SpecializedCollections.SingletonEnumerable(_relationship); } - } + => [_relationship]; public override IPeekResultSource GetOrCreateResultSource(string relationshipName) => new ResultSource(this); diff --git a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemFactory.cs b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemFactory.cs index d275f78d275f5..a81d9ab3434de 100644 --- a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemFactory.cs +++ b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemFactory.cs @@ -94,7 +94,7 @@ public async Task> GetPeekableItemsAsync( } } - return results.ToImmutable(); + return results.ToImmutableAndClear(); } } } diff --git a/src/EditorFeatures/Core.Wpf/Preview/AbstractPreviewTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/Preview/AbstractPreviewTaggerProvider.cs index 333328614e413..5d03c0d282277 100644 --- a/src/EditorFeatures/Core.Wpf/Preview/AbstractPreviewTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/Preview/AbstractPreviewTaggerProvider.cs @@ -50,7 +50,7 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection span return intersection.Select(s => new TagSpan(s, _tagInstance)); } - return SpecializedCollections.EmptyEnumerable>(); + return []; } public event EventHandler TagsChanged = (s, e) => { }; diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/ClassificationFormatDefinitions.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/ClassificationFormatDefinitions.cs new file mode 100644 index 0000000000000..1cd98c1ce6738 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/ClassificationFormatDefinitions.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.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Language.StandardClassification; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.QuickInfo; + +internal sealed class ClassificationFormatDefinitions +{ + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = ClassificationTypeDefinitions.ReducedEmphasisText)] + [Name(ClassificationTypeDefinitions.ReducedEmphasisText)] + [Order(After = Priority.High)] + [UserVisible(false)] + private class ReducedEmphasisTextFormat : ClassificationFormatDefinition + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ReducedEmphasisTextFormat() + { + this.ForegroundOpacity = 0.65f; + } + } +} + +internal sealed class ClassificationTypeDefinitions +{ + // Only used for theming, does not need localized + public const string ReducedEmphasisText = "Reduced Emphasis Text"; + + [Export] + [Name(ReducedEmphasisText)] + [BaseDefinition(PredefinedClassificationTypeNames.Text)] + internal readonly ClassificationTypeDefinition? ReducedEmphasisTextTypeDefinition; +} diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/LazyToolTip.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/LazyToolTip.cs index e79e7b06eb3f8..0e025b8c24dfe 100644 --- a/src/EditorFeatures/Core.Wpf/QuickInfo/LazyToolTip.cs +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/LazyToolTip.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Windows; using System.Windows.Controls; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; @@ -18,9 +19,10 @@ internal partial class ContentControlService /// /// Class which allows us to provide a delay-created tooltip for our reference entries. /// - private class LazyToolTip : ForegroundThreadAffinitizedObject + private sealed class LazyToolTip { private readonly Func _createToolTip; + private readonly IThreadingContext _threadingContext; private readonly FrameworkElement _element; private DisposableToolTip _disposableToolTip; @@ -29,11 +31,13 @@ private LazyToolTip( IThreadingContext threadingContext, FrameworkElement element, Func createToolTip) - : base(threadingContext, assertIsForeground: true) { + _threadingContext = threadingContext; _element = element; _createToolTip = createToolTip; + _threadingContext.ThrowIfNotOnUIThread(); + // Set ourselves as the tooltip of this text block. This will let WPF know that // it should attempt to show tooltips here. When WPF wants to show the tooltip // though we'll hear about it "ToolTipOpening". When that happens, we'll swap @@ -53,7 +57,7 @@ private void OnToolTipOpening(object sender, ToolTipEventArgs e) { try { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); Debug.Assert(_element.ToolTip == this); Debug.Assert(_disposableToolTip == null); @@ -71,7 +75,7 @@ private void OnToolTipClosing(object sender, ToolTipEventArgs e) { try { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); Debug.Assert(_disposableToolTip != null); Debug.Assert(_element.ToolTip == _disposableToolTip.ToolTip); diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsState.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsState.cs new file mode 100644 index 0000000000000..540c8cfcf9818 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsState.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. + +namespace Microsoft.CodeAnalysis.Editor.QuickInfo; + +/// +/// Represents the potential states of the view. +/// +internal enum OnTheFlyDocsState +{ + /// + /// The view is displaying the on-demand hyperlink. + /// + OnDemandLink, + + /// + /// The view is in the loading state. + /// + Loading, + + /// + /// The view is displaying computed results. + /// + Finished, +} diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml new file mode 100644 index 0000000000000..bee701ec0c2d6 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs new file mode 100644 index 0000000000000..63143db5fd634 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs @@ -0,0 +1,232 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Copilot; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.QuickInfo; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Text.Adornments; +using Microsoft.VisualStudio.Text.Editor; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.QuickInfo; + +/// +/// Interaction logic for OnTheFlyDocsView.xaml. +/// +internal sealed partial class OnTheFlyDocsView : UserControl, INotifyPropertyChanged +{ + private readonly ITextView _textView; + private readonly IViewElementFactoryService _viewElementFactoryService; + private readonly IAsynchronousOperationListener _asyncListener; + private readonly IAsyncQuickInfoSession _asyncQuickInfoSession; + private readonly IThreadingContext _threadingContext; + private readonly Document _document; + private readonly OnTheFlyDocsElement _onTheFlyDocsElement; + private readonly ContentControl _responseControl = new(); + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + private OnTheFlyDocsState _currentState = OnTheFlyDocsState.OnDemandLink; + + /// + public event PropertyChangedEventHandler? PropertyChanged; + + /// + /// Event that fires when the user requests results. + /// + public event EventHandler ResultsRequested; + +#pragma warning disable CA1822 // Mark members as static + /// + /// Used to display the "On the fly documentation" directly in the associated XAML file. + /// + public string OnTheFlyDocumentation => EditorFeaturesResources.On_the_fly_documentation; +#pragma warning restore CA1822 // Mark members as static + + public OnTheFlyDocsView(ITextView textView, IViewElementFactoryService viewElementFactoryService, IAsynchronousOperationListenerProvider listenerProvider, IAsyncQuickInfoSession asyncQuickInfoSession, IThreadingContext threadingContext, EditorFeaturesOnTheFlyDocsElement editorFeaturesOnTheFlyDocsElement) + { + _textView = textView; + _viewElementFactoryService = viewElementFactoryService; + _asyncListener = listenerProvider.GetListener(FeatureAttribute.OnTheFlyDocs); + _asyncQuickInfoSession = asyncQuickInfoSession; + _threadingContext = threadingContext; + _onTheFlyDocsElement = editorFeaturesOnTheFlyDocsElement.OnTheFlyDocsElement; + _document = editorFeaturesOnTheFlyDocsElement.Document; + + var sparkle = new ImageElement(new VisualStudio.Core.Imaging.ImageId(CopilotConstants.CopilotIconMonikerGuid, CopilotConstants.CopilotIconSparkleId)); + + OnDemandLinkContent = ToUIElement( + new ContainerElement( + ContainerElementStyle.Wrapped, + new object[] + { + sparkle, + ClassifiedTextElement.CreateHyperlink(EditorFeaturesResources.Tell_me_more, EditorFeaturesResources.Show_an_AI_generated_summary_of_this_code, () => + RequestResults()), + })); + + LoadingContent = ToUIElement( + new ContainerElement( + ContainerElementStyle.Stacked, + new object[] + { + new ClassifiedTextElement(new ClassifiedTextRun( + ClassificationTypeDefinitions.ReducedEmphasisText, EditorFeaturesResources.GitHub_Copilot_thinking)), + new SmoothProgressBar { IsIndeterminate = true, Height = 2, Margin = new Thickness { Top = 2 } }, + })); + + // Ensure the loading content stretches so that the progress bar + // takes the entire width of the quick info tooltip. + if (LoadingContent is FrameworkElement element) + { + element.HorizontalAlignment = HorizontalAlignment.Stretch; + } + + ResultsContent = ToUIElement( + new ContainerElement( + ContainerElementStyle.Stacked, + new object[] + { + new ContainerElement( + ContainerElementStyle.Wrapped, + new object[] + { + sparkle, + ClassifiedTextElement.CreatePlainText(EditorFeaturesResources.GitHub_Copilot), + }), + new ThematicBreakElement(), + _responseControl, + new ThematicBreakElement(), + new ClassifiedTextElement(new ClassifiedTextRun( + ClassificationTypeDefinitions.ReducedEmphasisText, EditorFeaturesResources.AI_generated_content_may_be_inaccurate)), + })); + + ResultsRequested += (_, _) => PopulateAIDocumentationElements(_cancellationTokenSource.Token); + _asyncQuickInfoSession.StateChanged += (_, _) => OnQuickInfoSessionChanged(); + InitializeComponent(); + } + + /// + /// Retrieves the documentation for the given symbol from the Copilot service and displays it in the view. + /// + private void PopulateAIDocumentationElements(CancellationToken cancellationToken) + { + var token = _asyncListener.BeginAsyncOperation(nameof(SetResultTextAsync)); + var copilotService = _document.GetLanguageService(); + if (copilotService is not null) + { + _ = SetResultTextAsync(copilotService, cancellationToken).CompletesAsyncOperation(token); + } + } + + private async Task SetResultTextAsync(ICopilotCodeAnalysisService copilotService, CancellationToken cancellationToken) + { + var stopwatch = SharedStopwatch.StartNew(); + + try + { + var response = await copilotService.GetOnTheFlyDocsAsync(_onTheFlyDocsElement.SymbolSignature, _onTheFlyDocsElement.DeclarationCode, _onTheFlyDocsElement.Language, cancellationToken).ConfigureAwait(false); + var copilotRequestTime = stopwatch.Elapsed; + + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); + + if (response is null || response.Length == 0) + { + SetResultText(EditorFeaturesResources.An_error_occurred_while_generating_documentation_for_this_code); + CurrentState = OnTheFlyDocsState.Finished; + Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Error_Displayed, KeyValueLogMessage.Create(m => + { + m["ElapsedTime"] = copilotRequestTime; + }, LogLevel.Information)); + } + else + { + SetResultText(response); + CurrentState = OnTheFlyDocsState.Finished; + + Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Results_Displayed, KeyValueLogMessage.Create(m => + { + m["ElapsedTime"] = copilotRequestTime; + m["ResponseLength"] = response.Length; + }, LogLevel.Information)); + } + } + catch (OperationCanceledException) + { + Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Results_Canceled, KeyValueLogMessage.Create(m => + { + m["ElapsedTime"] = stopwatch.Elapsed; + }, LogLevel.Information)); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } + } + + private void OnQuickInfoSessionChanged() + { + if (_asyncQuickInfoSession.State == QuickInfoSessionState.Dismissed) + { + _cancellationTokenSource.Cancel(); + } + } + + public OnTheFlyDocsState CurrentState + { + get => _currentState; + set => OnPropertyChanged(ref _currentState, value); + } + + public UIElement OnDemandLinkContent { get; } + + public UIElement LoadingContent { get; } + + public UIElement ResultsContent { get; } + + public void RequestResults() + { + CurrentState = OnTheFlyDocsState.Loading; + Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Loading_State_Entered, KeyValueLogMessage.Create(m => + { + m["SymbolHeaderText"] = _onTheFlyDocsElement.SymbolSignature; + }, LogLevel.Information)); + + ResultsRequested?.Invoke(this, EventArgs.Empty); + } + + /// + /// Sets the text of the AI-generated response. + /// + /// Response text to display. + public void SetResultText(string text) + { + _responseControl.Content = ToUIElement( + new ContainerElement(ContainerElementStyle.Wrapped, new ClassifiedTextElement([new ClassifiedTextRun(ClassificationTypeNames.Text, text)]))); + } + + private void OnPropertyChanged(ref T member, T value, [CallerMemberName] string? name = null) + { + member = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + + private UIElement ToUIElement(object model) + => _viewElementFactoryService.CreateViewElement(_textView, model); +} diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewFactory.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewFactory.cs new file mode 100644 index 0000000000000..8a4d463280287 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewFactory.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.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; +using Microsoft.CodeAnalysis.Editor.QuickInfo; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text.Adornments; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.CodeAnalysis.QuickInfo; + +[Export(typeof(IViewElementFactory))] +[Name("OnTheFlyDocsElement converter")] +[TypeConversion(from: typeof(EditorFeaturesOnTheFlyDocsElement), to: typeof(UIElement))] +[Order(Before = "Default")] +internal sealed class OnTheFlyDocsViewFactory : IViewElementFactory +{ + private readonly IViewElementFactoryService _factoryService; + private readonly IAsynchronousOperationListenerProvider _listenerProvider; + private readonly IAsyncQuickInfoBroker _asyncQuickInfoBroker; + private readonly IThreadingContext _threadingContext; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public OnTheFlyDocsViewFactory(IViewElementFactoryService factoryService, IAsynchronousOperationListenerProvider listenerProvider, IAsyncQuickInfoBroker asyncQuickInfoBroker, IThreadingContext threadingContext) + { + _factoryService = factoryService; + _listenerProvider = listenerProvider; + _asyncQuickInfoBroker = asyncQuickInfoBroker; + _threadingContext = threadingContext; + } + + public TView? CreateViewElement(ITextView textView, object model) where TView : class + { + if (typeof(TView) != typeof(UIElement)) + { + throw new InvalidOperationException("TView must be UIElement"); + } + + var editorFeaturesOnTheFlyDocsElement = (EditorFeaturesOnTheFlyDocsElement)model; + + Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Showed_Link, KeyValueLogMessage.Create(m => + { + m["SymbolHeaderText"] = editorFeaturesOnTheFlyDocsElement.OnTheFlyDocsElement.SymbolSignature; + }, LogLevel.Information)); + + var quickInfoSession = _asyncQuickInfoBroker.GetSession(textView); + + if (quickInfoSession is null) + { + throw new InvalidOperationException("QuickInfoSession is null"); + } + + return new OnTheFlyDocsView(textView, _factoryService, _listenerProvider, quickInfoSession, _threadingContext, editorFeaturesOnTheFlyDocsElement) as TView; + } +} diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewStateVisibilityConverter.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewStateVisibilityConverter.cs new file mode 100644 index 0000000000000..713ccc99ce6e0 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewStateVisibilityConverter.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Microsoft.CodeAnalysis.Editor.QuickInfo; + +/// +/// Converts the of the view to a value. +/// +internal sealed class OnTheFlyDocsViewStateVisibilityConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + => value is OnTheFlyDocsState state && parameter is OnTheFlyDocsState targetState && state == targetState ? Visibility.Visible : Visibility.Collapsed; + + public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) + => throw new NotImplementedException(); +} diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs index a0bed1d52547c..6c18010ae0371 100644 --- a/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs @@ -22,8 +22,9 @@ namespace Microsoft.CodeAnalysis.Editor.QuickInfo /// used to create a projection buffer out that will then be displayed in the quick info /// window. /// - internal class ProjectionBufferContent : ForegroundThreadAffinitizedObject + internal sealed class ProjectionBufferContent { + private readonly IThreadingContext _threadingContext; private readonly ImmutableArray _spans; private readonly IProjectionBufferFactoryService _projectionBufferFactoryService; private readonly EditorOptionsService _editorOptionsService; @@ -39,8 +40,8 @@ private ProjectionBufferContent( ITextEditorFactoryService textEditorFactoryService, IContentType contentType = null, ITextViewRoleSet roleSet = null) - : base(threadingContext) { + _threadingContext = threadingContext; _spans = spans; _projectionBufferFactoryService = projectionBufferFactoryService; _editorOptionsService = editorOptionsService; @@ -72,7 +73,7 @@ public static ViewHostingControl Create( private ViewHostingControl Create() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); return new ViewHostingControl(CreateView, CreateBuffer); } @@ -81,7 +82,7 @@ private IWpfTextView CreateView(ITextBuffer buffer) { var view = _textEditorFactoryService.CreateTextView(buffer, _roleSet); - view.SizeToFit(ThreadingContext); + view.SizeToFit(_threadingContext); view.Background = Brushes.Transparent; // Zoom out a bit to shrink the text. @@ -96,7 +97,7 @@ private IWpfTextView CreateView(ITextBuffer buffer) private IProjectionBuffer CreateBuffer() { return _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( - _editorOptionsService.Factory.GlobalOptions, _contentType, _spans.ToArray()); + _editorOptionsService.Factory.GlobalOptions, _contentType, [.. _spans]); } } } diff --git a/src/EditorFeatures/Core.Wpf/SignatureHelp/AbstractSignatureHelpCommandHandler.cs b/src/EditorFeatures/Core.Wpf/SignatureHelp/AbstractSignatureHelpCommandHandler.cs index 8106e43c6597b..bebf05786b1f2 100644 --- a/src/EditorFeatures/Core.Wpf/SignatureHelp/AbstractSignatureHelpCommandHandler.cs +++ b/src/EditorFeatures/Core.Wpf/SignatureHelp/AbstractSignatureHelpCommandHandler.cs @@ -5,19 +5,18 @@ #nullable disable using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHelp; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Editor.Options; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Text.Editor.Commanding; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; -using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Editor.CommandHandlers { - internal abstract class AbstractSignatureHelpCommandHandler : - ForegroundThreadAffinitizedObject + internal abstract class AbstractSignatureHelpCommandHandler { + protected readonly IThreadingContext ThreadingContext; private readonly SignatureHelpControllerProvider _controllerProvider; private readonly IGlobalOptionService _globalOptions; @@ -25,15 +24,15 @@ public AbstractSignatureHelpCommandHandler( IThreadingContext threadingContext, SignatureHelpControllerProvider controllerProvider, IGlobalOptionService globalOptions) - : base(threadingContext) { + ThreadingContext = threadingContext; _controllerProvider = controllerProvider; _globalOptions = globalOptions; } protected bool TryGetController(EditorCommandArgs args, out Controller controller) { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); // If args is `InvokeSignatureHelpCommandArgs` then sig help was explicitly invoked by the user and should // be shown whether or not the option is set. diff --git a/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpClassifier.cs b/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpClassifier.cs index c0123833a8f5b..dfff4bb55c4a6 100644 --- a/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpClassifier.cs +++ b/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpClassifier.cs @@ -55,7 +55,7 @@ public IList GetClassificationSpans(SnapshotSpan span) } } - return SpecializedCollections.EmptyList(); + return []; } } } diff --git a/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpPresenterSession.cs b/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpPresenterSession.cs index 2c45e666c4a68..f70d979217a8a 100644 --- a/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpPresenterSession.cs +++ b/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpPresenterSession.cs @@ -20,10 +20,14 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHel { internal partial class SignatureHelpPresenter { - private class SignatureHelpPresenterSession : ForegroundThreadAffinitizedObject, ISignatureHelpPresenterSession + private sealed class SignatureHelpPresenterSession( + IThreadingContext threadingContext, + ISignatureHelpBroker sigHelpBroker, + ITextView textView) : ISignatureHelpPresenterSession { - private readonly ISignatureHelpBroker _sigHelpBroker; - private readonly ITextView _textView; + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly ISignatureHelpBroker _sigHelpBroker = sigHelpBroker; + private readonly ITextView _textView = textView; public event EventHandler Dismissed; public event EventHandler ItemSelected; @@ -38,16 +42,6 @@ private class SignatureHelpPresenterSession : ForegroundThreadAffinitizedObject, public bool EditorSessionIsActive => _editorSessionOpt?.IsDismissed == false; - public SignatureHelpPresenterSession( - IThreadingContext threadingContext, - ISignatureHelpBroker sigHelpBroker, - ITextView textView) - : base(threadingContext) - { - _sigHelpBroker = sigHelpBroker; - _textView = textView; - } - public void PresentItems( ITrackingSpan triggerSpan, IList signatureHelpItems, @@ -150,13 +144,13 @@ private static int GetParameterIndexForItem(SignatureHelpItem item, int? selecte private void OnEditorSessionDismissed() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); this.Dismissed?.Invoke(this, new EventArgs()); } private void OnSelectedSignatureChanged(object sender, SelectedSignatureChangedEventArgs eventArgs) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_ignoreSelectionStatusChangedEvent) { @@ -174,7 +168,7 @@ private void OnSelectedSignatureChanged(object sender, SelectedSignatureChangedE public void Dismiss() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_editorSessionOpt == null) { diff --git a/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpSource.cs b/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpSource.cs index c8912a35b112b..0d83e3b5d4b91 100644 --- a/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpSource.cs +++ b/src/EditorFeatures/Core.Wpf/SignatureHelp/Presentation/SignatureHelpPresenter.SignatureHelpSource.cs @@ -5,6 +5,7 @@ #nullable disable using System.Collections.Generic; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.VisualStudio.Language.Intellisense; @@ -12,16 +13,11 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHel { internal partial class SignatureHelpPresenter { - private class SignatureHelpSource : ForegroundThreadAffinitizedObject, ISignatureHelpSource + private sealed class SignatureHelpSource(IThreadingContext threadingContext) : ISignatureHelpSource { - public SignatureHelpSource(IThreadingContext threadingContext) - : base(threadingContext) - { - } - public void AugmentSignatureHelpSession(ISignatureHelpSession session, IList signatures) { - AssertIsForeground(); + threadingContext.ThrowIfNotOnUIThread(); if (!session.Properties.TryGetProperty(s_augmentSessionKey, out var presenterSession)) { return; @@ -33,7 +29,7 @@ public void AugmentSignatureHelpSession(ISignatureHelpSession session, IList(TCommandArgs args, out ICommandHandler commandHandler) where TCommandArgs : EditorCommandArgs { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (!TryGetController(args, out var controller)) { commandHandler = null; @@ -77,7 +78,7 @@ private CommandState GetCommandStateWorker( Func nextHandler) where TCommandArgs : EditorCommandArgs { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); return TryGetControllerCommandHandler(args, out var commandHandler) ? commandHandler.GetCommandState(args, nextHandler) : nextHandler(); @@ -89,7 +90,7 @@ private void ExecuteCommandWorker( CommandExecutionContext context) where TCommandArgs : EditorCommandArgs { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (!TryGetControllerCommandHandler(args, out var commandHandler)) { nextHandler(); @@ -102,25 +103,25 @@ private void ExecuteCommandWorker( CommandState IChainedCommandHandler.GetCommandState(TypeCharCommandArgs args, Func nextHandler) { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); return GetCommandStateWorker(args, nextHandler); } void IChainedCommandHandler.ExecuteCommand(TypeCharCommandArgs args, Action nextHandler, CommandExecutionContext context) { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); ExecuteCommandWorker(args, nextHandler, context); } CommandState IChainedCommandHandler.GetCommandState(InvokeSignatureHelpCommandArgs args, Func nextHandler) { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); return CommandState.Available; } void IChainedCommandHandler.ExecuteCommand(InvokeSignatureHelpCommandArgs args, Action nextHandler, CommandExecutionContext context) { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); ExecuteCommandWorker(args, nextHandler, context); } } diff --git a/src/EditorFeatures/Core.Wpf/SignatureHelp/SignatureHelpControllerProvider.cs b/src/EditorFeatures/Core.Wpf/SignatureHelp/SignatureHelpControllerProvider.cs index 3f29cbce1ae38..3023a34d66205 100644 --- a/src/EditorFeatures/Core.Wpf/SignatureHelp/SignatureHelpControllerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/SignatureHelp/SignatureHelpControllerProvider.cs @@ -20,39 +20,29 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHelp { - [Export] - [Shared] - internal class SignatureHelpControllerProvider : ForegroundThreadAffinitizedObject + [Export, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class SignatureHelpControllerProvider( + IGlobalOptionService globalOptions, + IThreadingContext threadingContext, + [ImportMany] IEnumerable> signatureHelpProviders, + [ImportMany] IEnumerable, OrderableMetadata>> signatureHelpPresenters, + IAsyncCompletionBroker completionBroker, + IAsynchronousOperationListenerProvider listenerProvider) { private static readonly object s_controllerPropertyKey = new(); - private readonly IGlobalOptionService _globalOptions; - private readonly IIntelliSensePresenter _signatureHelpPresenter; - private readonly IAsynchronousOperationListener _listener; - private readonly IAsyncCompletionBroker _completionBroker; - private readonly IList> _signatureHelpProviders; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SignatureHelpControllerProvider( - IGlobalOptionService globalOptions, - IThreadingContext threadingContext, - [ImportMany] IEnumerable> signatureHelpProviders, - [ImportMany] IEnumerable, OrderableMetadata>> signatureHelpPresenters, - IAsyncCompletionBroker completionBroker, - IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext) - { - _globalOptions = globalOptions; - _signatureHelpPresenter = ExtensionOrderer.Order(signatureHelpPresenters).Select(lazy => lazy.Value).FirstOrDefault(); - _listener = listenerProvider.GetListener(FeatureAttribute.SignatureHelp); - _completionBroker = completionBroker; - _signatureHelpProviders = ExtensionOrderer.Order(signatureHelpProviders); - } + private readonly IGlobalOptionService _globalOptions = globalOptions; + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly IIntelliSensePresenter _signatureHelpPresenter = ExtensionOrderer.Order(signatureHelpPresenters).Select(lazy => lazy.Value).FirstOrDefault(); + private readonly IAsynchronousOperationListener _listener = listenerProvider.GetListener(FeatureAttribute.SignatureHelp); + private readonly IAsyncCompletionBroker _completionBroker = completionBroker; + private readonly IList> _signatureHelpProviders = ExtensionOrderer.Order(signatureHelpProviders); public Controller? GetController(ITextView textView, ITextBuffer subjectBuffer) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); // If we don't have a presenter, then there's no point in us even being involved. if (_signatureHelpPresenter == null) @@ -68,19 +58,19 @@ public SignatureHelpControllerProvider( private Controller GetControllerSlow(ITextView textView, ITextBuffer subjectBuffer) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); return textView.GetOrCreatePerSubjectBufferProperty( subjectBuffer, s_controllerPropertyKey, (textView, subjectBuffer) => new Controller( _globalOptions, - ThreadingContext, + _threadingContext, textView, subjectBuffer, _signatureHelpPresenter, _listener, - new DocumentProvider(ThreadingContext), + new DocumentProvider(_threadingContext), _signatureHelpProviders, _completionBroker)); } diff --git a/src/EditorFeatures/Core.Wpf/StringIndentation/StringIndentationAdornmentManager.VisibleBlock.cs b/src/EditorFeatures/Core.Wpf/StringIndentation/StringIndentationAdornmentManager.VisibleBlock.cs index d7deaeeef5cce..6f1533b0da943 100644 --- a/src/EditorFeatures/Core.Wpf/StringIndentation/StringIndentationAdornmentManager.VisibleBlock.cs +++ b/src/EditorFeatures/Core.Wpf/StringIndentation/StringIndentationAdornmentManager.VisibleBlock.cs @@ -163,7 +163,7 @@ private VisibleBlock(double x, ImmutableArray<(double start, double end)> ySegme if ((currentSegmentBottom - currentSegmentTop) >= MinLineHeight) segments.Add((currentSegmentTop, currentSegmentBottom)); - return segments.ToImmutable(); + return segments.ToImmutableAndClear(); } private static bool IsInHole(ImmutableArray orderedHoleSpans, ITextViewLine line) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/RefineUsingCopilot/RefineUsingCopilotSuggestedAction.cs b/src/EditorFeatures/Core.Wpf/Suggestions/RefineUsingCopilot/RefineUsingCopilotSuggestedAction.cs index d70d2d006f161..6c11c57dfebc7 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/RefineUsingCopilot/RefineUsingCopilotSuggestedAction.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/RefineUsingCopilot/RefineUsingCopilotSuggestedAction.cs @@ -44,13 +44,17 @@ private RefineUsingCopilotSuggestedAction( if (suggestedAction.OriginalDocument is not Document originalDocument) return null; - var copilotService = originalDocument.GetLanguageService(); - if (copilotService == null || !await copilotService.IsRefineOptionEnabledAsync().ConfigureAwait(false)) + if (originalDocument.GetLanguageService() is not { } optionsService || + await optionsService.IsRefineOptionEnabledAsync().ConfigureAwait(false) is false) + { return null; + } - var isAvailable = await copilotService.IsAvailableAsync(cancellationToken).ConfigureAwait(false); - if (!isAvailable) + if (originalDocument.GetLanguageService() is not { } copilotService || + await copilotService.IsAvailableAsync(cancellationToken).ConfigureAwait(false) is false) + { return null; + } return new RefineUsingCopilotSuggestedAction( suggestedAction.ThreadingContext, diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionWithNestedFlavors.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionWithNestedFlavors.cs index c1eb73aeb5fca..ec52af6c88e4c 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionWithNestedFlavors.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionWithNestedFlavors.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.PooledObjects; @@ -67,7 +68,7 @@ public sealed override async Task> GetActionSets cancellationToken.ThrowIfCancellationRequested(); // Light bulb will always invoke this property on the UI thread. - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (_nestedFlavors.IsDefault) { @@ -144,7 +145,7 @@ public override async Task GetPreviewAsync(CancellationToken cancellatio cancellationToken.ThrowIfCancellationRequested(); // Light bulb will always invoke this function on the UI thread. - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); var previewPaneService = Workspace.Services.GetService(); if (previewPaneService == null) @@ -170,7 +171,7 @@ public override async Task GetPreviewAsync(CancellationToken cancellatio else { // TakeNextPreviewAsync() needs to run on UI thread. - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); return await previewResult.GetPreviewsAsync(preferredDocumentId, preferredProjectId, cancellationToken).ConfigureAwait(true); } @@ -178,7 +179,7 @@ public override async Task GetPreviewAsync(CancellationToken cancellatio }, defaultValue: null, cancellationToken).ConfigureAwait(true); // GetPreviewPane() needs to run on the UI thread. - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); return previewPaneService.GetPreviewPane(GetDiagnostic(), previewContents); } diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs index 0fae407d9fc02..a89df6cd37880 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeFixesAndRefactorings; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Extensions; @@ -32,8 +33,9 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions /// /// Base class for all Roslyn light bulb menu items. /// - internal abstract partial class SuggestedAction : ForegroundThreadAffinitizedObject, ISuggestedAction3, IEquatable + internal abstract partial class SuggestedAction : ISuggestedAction3, IEquatable { + protected readonly IThreadingContext ThreadingContext; protected readonly SuggestedActionsSourceProvider SourceProvider; protected readonly Workspace Workspace; @@ -53,11 +55,11 @@ internal SuggestedAction( ITextBuffer subjectBuffer, object provider, CodeAction codeAction) - : base(threadingContext) { Contract.ThrowIfNull(provider); Contract.ThrowIfNull(codeAction); + ThreadingContext = threadingContext; SourceProvider = sourceProvider; Workspace = workspace; OriginalSolution = originalSolution; @@ -156,7 +158,7 @@ private async Task InvokeWorkerAsync(IProgress progressTra operations = await GetOperationsAsync(progressTracker, cancellationToken).ConfigureAwait(true); } - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (operations != null) { diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 885ccbc2995fd..f4f2be82f379a 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -21,6 +21,7 @@ using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using IUIThreadOperationContext = Microsoft.VisualStudio.Utilities.IUIThreadOperationContext; @@ -28,7 +29,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions { internal partial class SuggestedActionsSourceProvider { - private sealed partial class SuggestedActionsSource : ForegroundThreadAffinitizedObject, ISuggestedActionsSource3 + private sealed partial class SuggestedActionsSource : ISuggestedActionsSource3 { private readonly ISuggestedActionCategoryRegistryService _suggestedActionCategoryRegistry; @@ -37,6 +38,7 @@ private sealed partial class SuggestedActionsSource : ForegroundThreadAffinitize public event EventHandler? SuggestedActionsChanged { add { } remove { } } + private readonly IThreadingContext _threadingContext; public readonly IGlobalOptionService GlobalOptions; public SuggestedActionsSource( @@ -47,8 +49,8 @@ public SuggestedActionsSource( ITextBuffer textBuffer, ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, IAsynchronousOperationListener listener) - : base(threadingContext) { + _threadingContext = threadingContext; GlobalOptions = globalOptions; _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; @@ -59,11 +61,7 @@ public SuggestedActionsSource( } public void Dispose() - { - _state.Dispose(); - } - - private ReferenceCountedDisposable SourceState => _state; + => _state.Dispose(); public bool TryGetTelemetryId(out Guid telemetryId) { @@ -121,21 +119,6 @@ public bool TryGetTelemetryId(out Guid telemetryId) IUIThreadOperationContext operationContext) => null; - private static string GetFixCategory(DiagnosticSeverity severity) - { - switch (severity) - { - case DiagnosticSeverity.Hidden: - case DiagnosticSeverity.Info: - case DiagnosticSeverity.Warning: - return PredefinedSuggestedActionCategoryNames.CodeFix; - case DiagnosticSeverity.Error: - return PredefinedSuggestedActionCategoryNames.ErrorFix; - default: - throw ExceptionUtilities.Unreachable(); - } - } - public Task HasSuggestedActionsAsync( ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, @@ -145,146 +128,9 @@ public Task HasSuggestedActionsAsync( throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); } - private async Task GetSpanAsync(ReferenceCountedDisposable state, SnapshotSpan range, CancellationToken cancellationToken) - { - // First, ensure that the snapshot we're being asked about is for an actual - // roslyn document. This can fail, for example, in projection scenarios where - // we are called with a range snapshot that refers to the projection buffer - // and not the actual roslyn code that is being projected into it. - var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); - if (document == null) - { - return null; - } - - // Also make sure the range is from the same buffer that this source was created for - Contract.ThrowIfFalse( - range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), - $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); - - // Next, before we do any async work, acquire the user's selection, directly grabbing - // it from the UI thread if that's what we're on. That way we don't have any reentrancy - // blocking concerns if VS wants to block on this call (for example, if the user - // explicitly invokes the 'show smart tag' command). - // - // This work must happen on the UI thread as it needs to access the _textView's mutable - // state. - // - // Note: we may be called in one of two VS scenarios: - // 1) User has moved caret to a new line. In this case VS will call into us in the - // bg to see if we have any suggested actions for this line. In order to figure - // this out, we need to see what selection the user has (for refactorings), which - // necessitates going back to the fg. - // - // 2) User moves to a line and immediately hits ctrl-dot. In this case, on the UI - // thread VS will kick us off and then immediately block to get the results so - // that they can expand the light-bulb. In this case we cannot do BG work first, - // then call back into the UI thread to try to get the user selection. This will - // deadlock as the UI thread is blocked on us. - // - // There are two solution to '2'. Either introduce reentrancy (which we really don't - // like to do), or just ensure that we acquire and get the users selection up front. - // This means that when we're called from the UI thread, we never try to go back to the - // UI thread. - TextSpan? selection = null; - if (IsForeground()) - { - selection = TryGetCodeRefactoringSelection(state, range); - } - else - { - await InvokeBelowInputPriorityAsync(() => - { - // Make sure we were not disposed between kicking off this work and getting to this point. - using var state = _state.TryAddReference(); - if (state is null) - return; - - selection = TryGetCodeRefactoringSelection(state, range); - }, cancellationToken).ConfigureAwait(false); - } - - return selection; - } - - private static async Task GetFixLevelAsync( - ReferenceCountedDisposable state, - TextDocument document, - SnapshotSpan range, - CodeActionOptionsProvider fallbackOptions, - CancellationToken cancellationToken) - { - var lowPriorityAnalyzers = new ConcurrentSet(); - - foreach (var order in Orderings) - { - var priority = TryGetPriority(order); - Contract.ThrowIfNull(priority); - var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers); - - var result = await GetFixLevelAsync(priorityProvider).ConfigureAwait(false); - if (result != null) - return result; - } - - return null; - - async Task GetFixLevelAsync(ICodeActionRequestPriorityProvider priorityProvider) - { - if (state.Target.Owner._codeFixService != null && - state.Target.SubjectBuffer.SupportsCodeFixes()) - { - var result = await state.Target.Owner._codeFixService.GetMostSevereFixAsync( - document, range.Span.ToTextSpan(), priorityProvider, fallbackOptions, cancellationToken).ConfigureAwait(false); - - if (result.HasFix) - { - Logger.Log(FunctionId.SuggestedActions_HasSuggestedActionsAsync); - return GetFixCategory(result.CodeFixCollection.FirstDiagnostic.Severity); - } - - if (!result.UpToDate) - return null; - } - - return null; - } - } - - private async Task TryGetRefactoringSuggestedActionCategoryAsync( - TextDocument document, - TextSpan? selection, - CodeActionOptionsProvider fallbackOptions, - 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. - Trace.WriteLine("given range is not current"); - return null; - } - - if (GlobalOptions.GetOption(EditorComponentOnOffOptions.CodeRefactorings) && - state.Target.Owner._codeRefactoringService != null && - state.Target.SubjectBuffer.SupportsRefactorings()) - { - if (await state.Target.Owner._codeRefactoringService.HasRefactoringsAsync( - document, selection.Value, fallbackOptions, cancellationToken).ConfigureAwait(false)) - { - return PredefinedSuggestedActionCategoryNames.Refactoring; - } - } - - return null; - } - private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var selectedSpans = state.Target.TextView.Selection.SelectedSpans .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) @@ -293,17 +139,13 @@ await InvokeBelowInputPriorityAsync(() => // We only support refactorings when there is a single selection in the document. if (selectedSpans.Count != 1) - { return null; - } var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); // We only support refactorings when selected span intersects with the span that the light bulb is asking for. if (!translatedSpan.IntersectsWith(range)) - { return null; - } return translatedSpan.Span.ToTextSpan(); } @@ -317,6 +159,11 @@ private void OnTextViewClosed(object sender, EventArgs e) if (state is null) return null; + // Make sure the range is from the same buffer that this source was created for. + Contract.ThrowIfFalse( + range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), + $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); + var workspace = state.Target.Workspace; if (workspace == null) return null; @@ -335,18 +182,22 @@ private void OnTextViewClosed(object sender, EventArgs e) var fallbackOptions = GlobalOptions.GetCodeActionOptionsProvider(); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var linkedToken = linkedTokenSource.Token; + // Assign over cancellation token so no one accidentally uses the wrong token. + cancellationToken = linkedTokenSource.Token; - var errorTask = Task.Run(() => GetFixLevelAsync(state, document, range, fallbackOptions, linkedToken), linkedToken); + // Kick off the work to get errors. + var errorTask = GetFixLevelAsync(); - var selection = await GetSpanAsync(state, range, linkedToken).ConfigureAwait(false); + // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - var refactoringTask = SpecializedTasks.Null(); - if (selection != null) - { - refactoringTask = Task.Run( - () => TryGetRefactoringSuggestedActionCategoryAsync(document, selection, fallbackOptions, linkedToken), linkedToken); - } + var selection = TryGetCodeRefactoringSelection(state, range); + await TaskScheduler.Default; + + // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. + var refactoringTask = selection != null + ? TryGetRefactoringSuggestedActionCategoryAsync(selection) + : SpecializedTasks.Null(); // If we happen to get the result of the error task before the refactoring task, // and that result is non-null, we can just cancel the refactoring task. @@ -356,6 +207,79 @@ private void OnTextViewClosed(object sender, EventArgs e) return result == null ? null : _suggestedActionCategoryRegistry.CreateSuggestedActionCategorySet(result); + + async Task GetFixLevelAsync() + { + // Ensure we yield the thread that called into us, allowing it to continue onwards. + await TaskScheduler.Default.SwitchTo(alwaysYield: true); + var lowPriorityAnalyzers = new ConcurrentSet(); + + foreach (var order in Orderings) + { + var priority = TryGetPriority(order); + Contract.ThrowIfNull(priority); + var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers); + + var result = await GetFixCategoryAsync(priorityProvider).ConfigureAwait(false); + if (result != null) + return result; + } + + return null; + } + + async Task GetFixCategoryAsync(ICodeActionRequestPriorityProvider priorityProvider) + { + if (state.Target.Owner._codeFixService != null && + state.Target.SubjectBuffer.SupportsCodeFixes()) + { + var result = await state.Target.Owner._codeFixService.GetMostSevereFixAsync( + document, range.Span.ToTextSpan(), priorityProvider, fallbackOptions, cancellationToken).ConfigureAwait(false); + + if (result.HasFix) + { + Logger.Log(FunctionId.SuggestedActions_HasSuggestedActionsAsync); + return result.CodeFixCollection.FirstDiagnostic.Severity switch + { + + DiagnosticSeverity.Hidden or DiagnosticSeverity.Info or DiagnosticSeverity.Warning => PredefinedSuggestedActionCategoryNames.CodeFix, + DiagnosticSeverity.Error => PredefinedSuggestedActionCategoryNames.ErrorFix, + _ => throw ExceptionUtilities.Unreachable(), + }; + } + + if (!result.UpToDate) + return null; + } + + return null; + } + + async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) + { + // Ensure we yield the thread that called into us, allowing it to continue onwards. + await TaskScheduler.Default.SwitchTo(alwaysYield: true); + + if (!selection.HasValue) + { + // this is here to fail test and see why it is failed. + Trace.WriteLine("given range is not current"); + return null; + } + + if (GlobalOptions.GetOption(EditorComponentOnOffOptions.CodeRefactorings) && + state.Target.Owner._codeRefactoringService != null && + state.Target.SubjectBuffer.SupportsRefactorings()) + { + if (await state.Target.Owner._codeRefactoringService.HasRefactoringsAsync( + document, selection.Value, fallbackOptions, cancellationToken).ConfigureAwait(false)) + { + return PredefinedSuggestedActionCategoryNames.Refactoring; + } + } + + return null; + } } } } diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs index 0f3542b71feea..8745781c048a2 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs @@ -40,7 +40,7 @@ public async Task GetSuggestedActionsAsync( ImmutableArray collectors, CancellationToken cancellationToken) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); // We should only be called with the orderings we exported in order from highest pri to lowest pri. Contract.ThrowIfFalse(Orderings.SequenceEqual(collectors.SelectAsArray(c => c.Priority))); @@ -69,8 +69,8 @@ private async Task GetSuggestedActionsWorkerAsync( ArrayBuilder completedCollectors, CancellationToken cancellationToken) { - AssertIsForeground(); - using var state = SourceState.TryAddReference(); + _threadingContext.ThrowIfNotOnUIThread(); + using var state = _state.TryAddReference(); if (state is null) return; @@ -93,7 +93,7 @@ private async Task GetSuggestedActionsWorkerAsync( // especially important as we are sending disparate requests for diagnostics, and we do not want the // individual diagnostic requests to redo all the work to run source generators, create skeletons, // etc. - using var _1 = RemoteKeepAliveSession.Create(document.Project.Solution, _listener); + using var _1 = await RemoteKeepAliveSession.CreateAsync(document.Project.Solution, cancellationToken).ConfigureAwait(false); // Keep track of how many actions we've put in the lightbulb at each priority level. We do // this as each priority level will both sort and inline actions. However, we don't want to @@ -299,21 +299,21 @@ ISuggestedAction ConvertToSuggestedAction(IUnifiedSuggestedAction unifiedSuggest => unifiedSuggestedAction switch { UnifiedCodeFixSuggestedAction codeFixAction => new CodeFixSuggestedAction( - ThreadingContext, owner, codeFixAction.Workspace, originalDocument, subjectBuffer, + _threadingContext, owner, codeFixAction.Workspace, originalDocument, subjectBuffer, codeFixAction.CodeFix, codeFixAction.Provider, codeFixAction.OriginalCodeAction, ConvertToSuggestedActionSet(codeFixAction.FixAllFlavors, originalDocument)), UnifiedCodeRefactoringSuggestedAction codeRefactoringAction => new CodeRefactoringSuggestedAction( - ThreadingContext, owner, codeRefactoringAction.Workspace, originalDocument, subjectBuffer, + _threadingContext, owner, codeRefactoringAction.Workspace, originalDocument, subjectBuffer, codeRefactoringAction.CodeRefactoringProvider, codeRefactoringAction.OriginalCodeAction, ConvertToSuggestedActionSet(codeRefactoringAction.FixAllFlavors, originalDocument)), UnifiedFixAllCodeFixSuggestedAction fixAllAction => new FixAllCodeFixSuggestedAction( - ThreadingContext, owner, fixAllAction.Workspace, originalSolution, subjectBuffer, + _threadingContext, owner, fixAllAction.Workspace, originalSolution, subjectBuffer, fixAllAction.FixAllState, fixAllAction.Diagnostic, fixAllAction.OriginalCodeAction), UnifiedFixAllCodeRefactoringSuggestedAction fixAllCodeRefactoringAction => new FixAllCodeRefactoringSuggestedAction( - ThreadingContext, owner, fixAllCodeRefactoringAction.Workspace, originalSolution, subjectBuffer, + _threadingContext, owner, fixAllCodeRefactoringAction.Workspace, originalSolution, subjectBuffer, fixAllCodeRefactoringAction.FixAllState, fixAllCodeRefactoringAction.OriginalCodeAction), UnifiedSuggestedActionWithNestedActions nestedAction => new SuggestedActionWithNestedActions( - ThreadingContext, owner, nestedAction.Workspace, originalSolution, subjectBuffer, + _threadingContext, owner, nestedAction.Workspace, originalSolution, subjectBuffer, nestedAction.Provider ?? this, nestedAction.OriginalCodeAction, nestedAction.NestedActionSets.SelectAsArray(s => ConvertToSuggestedActionSet(s, originalDocument))), _ => throw ExceptionUtilities.Unreachable() diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf index 09984a6f812ca..127d6cb443c23 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf @@ -32,6 +32,11 @@ Výběr se spouští v okně Interactive. + + Generating suggestions... + Generování návrhů… + + Get AI-powered rename suggestions (Ctrl+Space) Získejte návrhy přejmenování využívající umělou inteligenci (Ctrl+Mezerník) @@ -67,6 +72,11 @@ Vlastnost CurrentWindow se dá přiřadit jenom jednou. + + Toggle rename suggestions (Ctrl+Space) + Přepnout návrhy na přejmenování (Ctrl+Mezerník) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf index de7e4622e7435..80e15c424d8ba 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf @@ -32,6 +32,11 @@ Die Auswahl wird im Interactive-Fenster ausgeführt. + + Generating suggestions... + Vorschläge werden generiert... + + Get AI-powered rename suggestions (Ctrl+Space) KI-gestützte Umbenennungsvorschläge abrufen (STRG+LEERTASTE) @@ -67,6 +72,11 @@ Die Eigenschaft "CurrentWindow" kann nur ein Mal zugewiesen werden. + + Toggle rename suggestions (Ctrl+Space) + Umbenennungsvorschläge umschalten (STRG+LEERTASTE) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf index 116149effe1f1..29fe71c06420a 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf @@ -32,6 +32,11 @@ Ejecutando selección en ventana interactiva. + + Generating suggestions... + Generando sugerencias... + + Get AI-powered rename suggestions (Ctrl+Space) Obtener sugerencias de cambio de nombre con tecnología de inteligencia artificial (Ctrl+Espacio) @@ -67,6 +72,11 @@ La propiedad CurrentWindow solo se puede asignar una vez. + + Toggle rename suggestions (Ctrl+Space) + Alternar sugerencias de cambio de nombre (Ctrl+Espacio) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf index 4319062bca8d4..1d0d62f9f6aa3 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf @@ -32,6 +32,11 @@ Exécution de la sélection dans la fenêtre interactive. + + Generating suggestions... + Génération en cours de suggestions... + + Get AI-powered rename suggestions (Ctrl+Space) Obtenir des suggestions de renommage basées sur l’intelligence artificielle (Ctrl+Espace) @@ -67,6 +72,11 @@ La propriété CurrentWindow ne peut être assignée qu'une seule fois. + + Toggle rename suggestions (Ctrl+Space) + Activer/désactiver les suggestions de changement de nom (Ctrl+Space) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf index 3f5deaf2491d7..840ef6ec07113 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf @@ -32,6 +32,11 @@ Esecuzione della selezione nella finestra interattiva. + + Generating suggestions... + Generazione di suggerimenti in corso + + Get AI-powered rename suggestions (Ctrl+Space) Ottieni suggerimenti di ridenominazione basati sull'intelligenza artificiale (CTRL+BARRA SPAZIATRICE) @@ -67,6 +72,11 @@ La proprietà CurrentWindow può essere assegnata una sola volta. + + Toggle rename suggestions (Ctrl+Space) + Attiva/Disattiva i suggerimenti di ridenominazione (CTRL+BARRA SPAZIATRICE) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf index fcfbc0b834c31..d279600b24680 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf @@ -32,6 +32,11 @@ インタラクティブ ウィンドウで選択を実行します。 + + Generating suggestions... + 候補を生成しています... + + Get AI-powered rename suggestions (Ctrl+Space) AI を利用した名前変更の提示を取得する (Ctrl + Space) @@ -67,6 +72,11 @@ CurrentWindow プロパティは 1 回のみ割り当てることができます。 + + Toggle rename suggestions (Ctrl+Space) + 名前変更の提示の切り替え (Ctrl + Space キー) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf index 5065613dadc48..4a4691d5dbec8 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf @@ -32,6 +32,11 @@ 선택 영역을 대화형 창에서 실행하는 중입니다. + + Generating suggestions... + 제안 사항을 생성하는 중... + + Get AI-powered rename suggestions (Ctrl+Space) AI 기반 이름 바꾸기 제안 보기(Ctrl+스페이스바) @@ -67,6 +72,11 @@ CurrentWindow 속성은 한 번만 할당될 수 있습니다. + + Toggle rename suggestions (Ctrl+Space) + 이름 바꾸기 제안 토글(Ctrl+스페이스바) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf index 23c17d9693380..636d9dd756e73 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf @@ -32,6 +32,11 @@ Wykonywanie zaznaczenia w oknie Interactive. + + Generating suggestions... + Generowanie sugestii... + + Get AI-powered rename suggestions (Ctrl+Space) Uzyskaj funkcję Zmień nazwy sugestii obsługiwane przez sztuczną inteligencję (Ctrl+Spacja) @@ -67,6 +72,11 @@ Właściwość CurrentWindow można przypisać tylko raz. + + Toggle rename suggestions (Ctrl+Space) + Przełącz zmiany nazwy sugestii (Ctrl+Spacja) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf index f634d8dae75ed..e76ab831cd398 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf @@ -32,6 +32,11 @@ Executando seleção na Janela Interativa. + + Generating suggestions... + Gerando sugestões... + + Get AI-powered rename suggestions (Ctrl+Space) Obter sugestões de renomeação com tecnologia de IA (Ctrl+Espaço) @@ -67,6 +72,11 @@ A propriedade CurrentWindow deve ser atribuído apenas uma vez. + + Toggle rename suggestions (Ctrl+Space) + Alternar sugestões de renomeação (Ctrl+Espaço) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf index e2a1e6d137a96..0821209d935d0 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf @@ -32,6 +32,11 @@ Выполнение выделенного фрагмента в Интерактивном окне. + + Generating suggestions... + Создаются предложения... + + Get AI-powered rename suggestions (Ctrl+Space) Подбор имени на базе искусственного интеллекта (CTRL+ПРОБЕЛ) @@ -67,6 +72,11 @@ Свойство CurrentWindow может быть назначено только один раз. + + Toggle rename suggestions (Ctrl+Space) + Переключение подбора имени (CTRL+ПРОБЕЛ) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf index 3ebd606908af6..58b1ccd2fb2be 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf @@ -32,6 +32,11 @@ Seçim, Etkileşimli Pencere'de yürütülüyor. + + Generating suggestions... + Öneriler oluşturuluyor... + + Get AI-powered rename suggestions (Ctrl+Space) Yapay zeka destekli yeniden adlandırma önerileri alın (Ctrl+Space) @@ -67,6 +72,11 @@ CurrentWindow özelliği yalnızca bir kez atanabilir. + + Toggle rename suggestions (Ctrl+Space) + Yeniden adlandırma önerilerini değiştir (Ctrl+Space) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf index 6d6dbb3d7f0e1..bf661f21aa9a4 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf @@ -32,6 +32,11 @@ 在交互窗口中执行所选内容。 + + Generating suggestions... + 正在生成建议... + + Get AI-powered rename suggestions (Ctrl+Space) 获取 AI 支持的重命名建议(Ctrl+空格键) @@ -67,6 +72,11 @@ 只能对 CurrentWindow 属性进行一次赋值。 + + Toggle rename suggestions (Ctrl+Space) + 切换重命名建议(Ctrl+空格) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf index 54c10200392fc..0ca22030737da 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf @@ -32,6 +32,11 @@ 正在於互動視窗中執行選取範圍。 + + Generating suggestions... + 正在產生建議... + + Get AI-powered rename suggestions (Ctrl+Space) 取得 AI 支援的重新命名建議 (Ctrl+Space) @@ -67,6 +72,11 @@ CurrentWindow 屬性只能受指派一次。 + + Toggle rename suggestions (Ctrl+Space) + 切換重新命名建議 (Ctrl+空格鍵) + + \ No newline at end of file diff --git a/src/EditorFeatures/Core/Classification/ClassificationTypeDefinitions.cs b/src/EditorFeatures/Core/Classification/ClassificationTypeDefinitions.cs index f7829ff3920bf..201fff367f69d 100644 --- a/src/EditorFeatures/Core/Classification/ClassificationTypeDefinitions.cs +++ b/src/EditorFeatures/Core/Classification/ClassificationTypeDefinitions.cs @@ -48,7 +48,7 @@ internal sealed class ClassificationTypeDefinitions #region User Types - Classes [Export] [Name(ClassificationTypeNames.ClassName)] - [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + [BaseDefinition(PredefinedClassificationTypeNames.Identifier)] internal readonly ClassificationTypeDefinition UserTypeClassesTypeDefinition; #endregion #region User Types - Records @@ -66,37 +66,37 @@ internal sealed class ClassificationTypeDefinitions #region User Types - Delegates [Export] [Name(ClassificationTypeNames.DelegateName)] - [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + [BaseDefinition(PredefinedClassificationTypeNames.Identifier)] internal readonly ClassificationTypeDefinition UserTypeDelegatesTypeDefinition; #endregion #region User Types - Enums [Export] [Name(ClassificationTypeNames.EnumName)] - [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + [BaseDefinition(PredefinedClassificationTypeNames.Identifier)] internal readonly ClassificationTypeDefinition UserTypeEnumsTypeDefinition; #endregion #region User Types - Interfaces [Export] [Name(ClassificationTypeNames.InterfaceName)] - [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + [BaseDefinition(PredefinedClassificationTypeNames.Identifier)] internal readonly ClassificationTypeDefinition UserTypeInterfacesTypeDefinition; #endregion #region User Types - Modules [Export] [Name(ClassificationTypeNames.ModuleName)] - [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + [BaseDefinition(PredefinedClassificationTypeNames.Identifier)] internal readonly ClassificationTypeDefinition UserTypeModulesTypeDefinition; #endregion #region User Types - Structures [Export] [Name(ClassificationTypeNames.StructName)] - [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + [BaseDefinition(PredefinedClassificationTypeNames.Identifier)] internal readonly ClassificationTypeDefinition UserTypeStructuresTypeDefinition; #endregion #region User Types - Type Parameters [Export] [Name(ClassificationTypeNames.TypeParameterName)] - [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + [BaseDefinition(PredefinedClassificationTypeNames.Identifier)] internal readonly ClassificationTypeDefinition UserTypeTypeParametersTypeDefinition; #endregion @@ -423,6 +423,13 @@ internal sealed class ClassificationTypeDefinitions internal readonly ClassificationTypeDefinition ReassignedVariableTypeDefinition; #endregion + #region Obsolete Symbol + [Export] + [Name(ClassificationTypeNames.ObsoleteSymbol)] + [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)] + internal readonly ClassificationTypeDefinition ObsoleteSymbolTypeDefinition; + #endregion + #region Static Symbol [Export] [Name(ClassificationTypeNames.StaticSymbol)] diff --git a/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs b/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs index 88b5464cdb662..5566e5a508276 100644 --- a/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs +++ b/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Tagging; @@ -25,10 +25,9 @@ namespace Microsoft.CodeAnalysis.Classification; internal partial class CopyPasteAndPrintingClassificationBufferTaggerProvider { - private sealed class Tagger : IAccurateTagger, IDisposable + public sealed class Tagger : IAccurateTagger, IDisposable { private readonly CopyPasteAndPrintingClassificationBufferTaggerProvider _owner; - private readonly ITextBuffer _subjectBuffer; private readonly ITaggerEventSource _eventSource; private readonly IGlobalOptionService _globalOptions; @@ -45,7 +44,6 @@ public Tagger( IGlobalOptionService globalOptions) { _owner = owner; - _subjectBuffer = subjectBuffer; _globalOptions = globalOptions; _eventSource = TaggerEventSources.Compose( @@ -88,89 +86,117 @@ private void OnEventSourceChanged(object? sender, TaggerEventArgs _) public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { - _owner._threadingContext.ThrowIfNotOnUIThread(); - // we never return any tags for GetTags. This tagger is only for 'Accurate' scenarios. return []; } + private static IEnumerable> GetIntersectingTags(NormalizedSnapshotSpanCollection spans, TagSpanIntervalTree cachedTags) + => SegmentedListPool>.ComputeList( + static (args, tags) => args.cachedTags.AddIntersectingTagSpans(args.spans, tags), + (cachedTags, spans)); + public IEnumerable> GetAllTags(NormalizedSnapshotSpanCollection spans, CancellationToken cancellationToken) { - _owner._threadingContext.ThrowIfNotOnUIThread(); if (spans.Count == 0) return []; - var firstSpan = spans.First(); - var snapshot = firstSpan.Snapshot; - Debug.Assert(snapshot.TextBuffer == _subjectBuffer); + var snapshot = spans.First().Snapshot; var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) return []; - var classificationService = document.GetLanguageService(); - if (classificationService == null) - return []; - // 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)); - GetCachedInfo(out var cachedTaggedSpan, out var cachedTags); + var (cachedTaggedSpan, cachedTags) = GetCachedInfo(); // We don't need to actually classify if what we're being asked for is a subspan // of the last classification we performed. - var canReuseCache = - cachedTaggedSpan?.Snapshot == snapshot && - cachedTaggedSpan.Value.Contains(spanToTag); + if (cachedTaggedSpan?.Snapshot == snapshot && + cachedTaggedSpan.Value.Contains(spanToTag)) + { + Contract.ThrowIfNull(cachedTags); + return GetIntersectingTags(spans, cachedTags); + } + else + { + return ComputeAndCacheAllTags(spans, snapshot, document, spanToTag, cancellationToken); + } + } + + private IEnumerable> ComputeAndCacheAllTags( + NormalizedSnapshotSpanCollection spans, + ITextSnapshot snapshot, + Document document, + SnapshotSpan spanToTag, + CancellationToken cancellationToken) + { + var classificationService = document.GetRequiredLanguageService(); + + // Our cache is not there, or is out of date. We need to compute the up to date results. + var options = _globalOptions.GetClassificationOptions(document.Project.Language); + + // Final list of tags to produce, containing syntax/semantic/embedded classification tags. + using var _ = SegmentedListPool.GetPooledList>(out var mergedTags); - if (!canReuseCache) + _owner._threadingContext.JoinableTaskFactory.Run(async () => { - // 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); - var options = _globalOptions.GetClassificationOptions(document.Project.Language); + // Defer to our helper which will compute syntax/semantic/embedded classifications, properly + // layering them into the final result we return. + await TotalClassificationAggregateTagger.AddTagsAsync( + new NormalizedSnapshotSpanCollection(spanToTag), + mergedTags, + // We should only be asking for a single span when getting the syntactic classifications + GetTaggingFunction(requireSingleSpan: true, (span, buffer) => classificationService.AddSyntacticClassificationsAsync(document, span, buffer, cancellationToken)), + // We should only be asking for a single span when getting the semantic classifications + GetTaggingFunction(requireSingleSpan: true, (span, buffer) => classificationService.AddSemanticClassificationsAsync(document, span, options, buffer, cancellationToken)), + // Note: many string literal spans may be passed in when getting embedded classifications + GetTaggingFunction(requireSingleSpan: false, (span, buffer) => classificationService.AddEmbeddedLanguageClassificationsAsync(document, span, options, buffer, cancellationToken)), + arg: default).ConfigureAwait(false); + }); + + var cachedTags = new TagSpanIntervalTree(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, mergedTags); - _owner._threadingContext.JoinableTaskFactory.Run(async () => - { - var snapshotSpan = new DocumentSnapshotSpan(document, spanToTag); + lock (_gate) + { + _cachedTaggedSpan = spanToTag; + _cachedTags = cachedTags; + } - // When copying/pasting, ensure we have classifications fully computed for the requested spans - // for both semantic classifications and embedded lang classifications. - await ProduceTagsAsync(context, snapshotSpan, classificationService, options, ClassificationType.Semantic, cancellationToken).ConfigureAwait(false); - await ProduceTagsAsync(context, snapshotSpan, classificationService, options, ClassificationType.EmbeddedLanguage, cancellationToken).ConfigureAwait(false); - }); + return GetIntersectingTags(spans, cachedTags); - cachedTaggedSpan = spanToTag; - cachedTags = new TagSpanIntervalTree(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, context.TagSpans); + Func>, VoidResult, Task> GetTaggingFunction( + bool requireSingleSpan, Func, Task> addTagsAsync) + { + Contract.ThrowIfTrue(requireSingleSpan && spans.Count != 1, "We should only be asking for a single span"); + return (spans, tempBuffer, _) => AddSpansAsync(spans, tempBuffer, addTagsAsync); + } + + async Task AddSpansAsync( + NormalizedSnapshotSpanCollection spans, + SegmentedList> result, + Func, Task> addAsync) + { + // temp buffer we can use across all our classification calls. Should be cleared between each call. + using var _ = Classifier.GetPooledList(out var tempBuffer); - lock (_gate) + foreach (var span in spans) { - _cachedTaggedSpan = cachedTaggedSpan; - _cachedTags = cachedTags; + tempBuffer.Clear(); + await addAsync(span.Span.ToTextSpan(), tempBuffer).ConfigureAwait(false); + + foreach (var classifiedSpan in tempBuffer) + result.Add(ClassificationUtilities.Convert(_owner._typeMap, snapshot, classifiedSpan)); } } - - return SegmentedListPool.ComputeList( - static (args, tags) => args.cachedTags?.AddIntersectingTagSpans(args.spans, tags), - (cachedTags, spans), - _: (ITagSpan?)null); } - private Task ProduceTagsAsync( - TaggerContext context, DocumentSnapshotSpan snapshotSpan, - IClassificationService classificationService, ClassificationOptions options, ClassificationType type, CancellationToken cancellationToken) - { - return ClassificationUtilities.ProduceTagsAsync( - context, snapshotSpan, classificationService, _owner._typeMap, options, type, cancellationToken); - } - - private void GetCachedInfo(out SnapshotSpan? cachedTaggedSpan, out TagSpanIntervalTree? cachedTags) + private (SnapshotSpan? cachedTaggedSpan, TagSpanIntervalTree? cachedTags) GetCachedInfo() { lock (_gate) - { - cachedTaggedSpan = _cachedTaggedSpan; - cachedTags = _cachedTags; - } + return (_cachedTaggedSpan, _cachedTags); } } } diff --git a/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.cs b/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.cs index 7679a7cb4ac9d..d432e03ee5723 100644 --- a/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.cs +++ b/src/EditorFeatures/Core/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.cs @@ -23,6 +23,9 @@ namespace Microsoft.CodeAnalysis.Classification; /// i.e. if you're printing, you want semantic classification even for code that's not in view. /// The same applies to copy/pasting. /// +/// +/// The returned from can be used on any thread. +/// [Export(typeof(ITaggerProvider))] [TagType(typeof(IClassificationTag))] [ContentType(ContentTypeNames.CSharpContentType)] @@ -40,7 +43,7 @@ internal partial class CopyPasteAndPrintingClassificationBufferTaggerProvider( private readonly ClassificationTypeMap _typeMap = typeMap; private readonly IGlobalOptionService _globalOptions = globalOptions; - public IAccurateTagger? CreateTagger(ITextBuffer buffer) where T : ITag + public Tagger? CreateTagger(ITextBuffer buffer) where T : ITag { _threadingContext.ThrowIfNotOnUIThread(); @@ -51,9 +54,9 @@ internal partial class CopyPasteAndPrintingClassificationBufferTaggerProvider( return null; } - return new Tagger(this, buffer, _asyncListener, _globalOptions) as IAccurateTagger; + return new Tagger(this, buffer, _asyncListener, _globalOptions); } ITagger? ITaggerProvider.CreateTagger(ITextBuffer buffer) - => CreateTagger(buffer); + => CreateTagger(buffer) as IAccurateTagger; } diff --git a/src/EditorFeatures/Core/Classification/Semantic/AbstractSemanticOrEmbeddedClassificationViewTaggerProvider.cs b/src/EditorFeatures/Core/Classification/Semantic/AbstractSemanticOrEmbeddedClassificationViewTaggerProvider.cs index 88b1ff86d4d79..393f3ed6ab7f1 100644 --- a/src/EditorFeatures/Core/Classification/Semantic/AbstractSemanticOrEmbeddedClassificationViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/Classification/Semantic/AbstractSemanticOrEmbeddedClassificationViewTaggerProvider.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.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.CodeAnalysis.Workspaces; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Classification; @@ -53,6 +61,28 @@ protected AbstractSemanticOrEmbeddedClassificationViewTaggerProvider( protected sealed override TaggerDelay EventChangeDelay => TaggerDelay.Short; + /// + /// We do classification in two passes. In the first pass we do not block getting classifications on building the + /// full compilation. This may take a significant amount of time and can cause a very latency sensitive operation + /// (copying) to block the user while we wait on this work to happen. + /// + /// It's also a better experience to get classifications to the user faster versus waiting a potentially large + /// amount of time waiting for all the compilation information to be built. For example, we can classify types that + /// we've parsed in other files, or partially loaded from metadata, even if we're still parsing/loading. For cross + /// language projects, this also produces semantic classifications more quickly as we do not have to wait on + /// skeletons to be built. + /// + /// + /// In the second pass though, we will go and do things without frozen-partial semantics, so that we do always snap + /// to a final correct state. Note: the expensive second pass will be kicked down the road as new events come in to + /// classify things. + /// + /// + protected sealed override bool SupportsFrozenPartialSemantics => true; + + protected override bool TagEquals(IClassificationTag tag1, IClassificationTag tag2) + => tag1.ClassificationType.Classification == tag2.ClassificationType.Classification; + protected sealed override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer) { this.ThreadingContext.ThrowIfNotOnUIThread(); @@ -63,39 +93,185 @@ protected sealed override ITaggerEventSource CreateEventSource(ITextView textVie TaggerEventSources.OnViewSpanChanged(ThreadingContext, textView), TaggerEventSources.OnWorkspaceChanged(subjectBuffer, AsyncListener), TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer), - TaggerEventSources.OnGlobalOptionChanged(_globalOptions, ClassificationOptionsStorage.ClassifyReassignedVariables)); + TaggerEventSources.OnGlobalOptionChanged(_globalOptions, ClassificationOptionsStorage.ClassifyReassignedVariables), + TaggerEventSources.OnGlobalOptionChanged(_globalOptions, ClassificationOptionsStorage.ClassifyObsoleteSymbols)); } - protected sealed override Task ProduceTagsAsync( + protected sealed override async Task ProduceTagsAsync( TaggerContext context, DocumentSnapshotSpan spanToTag, CancellationToken cancellationToken) { var document = spanToTag.Document; if (document == null) - return Task.CompletedTask; + return; // 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(); if (classificationService == null) - return Task.CompletedTask; + return; // 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.Services.GetRequiredService(); if (workspaceContextService?.IsInLspEditorContext() == true) - return Task.CompletedTask; + return; // If the LSP semantic tokens feature flag is enabled, return nothing to prevent conflicts. var isLspSemanticTokensEnabled = _globalOptions.GetOption(LspOptionsStorage.LspSemanticTokensFeatureFlag); if (isLspSemanticTokensEnabled) - return Task.CompletedTask; + return; var classificationOptions = _globalOptions.GetClassificationOptions(document.Project.Language); - return ClassificationUtilities.ProduceTagsAsync( - context, spanToTag, classificationService, _typeMap, classificationOptions, _type, cancellationToken); + await ProduceTagsAsync( + context, spanToTag, classificationService, classificationOptions, cancellationToken).ConfigureAwait(false); } - protected override bool TagEquals(IClassificationTag tag1, IClassificationTag tag2) - => tag1.ClassificationType.Classification == tag2.ClassificationType.Classification; + public async Task ProduceTagsAsync( + TaggerContext context, + DocumentSnapshotSpan spanToTag, + IClassificationService classificationService, + ClassificationOptions options, + CancellationToken cancellationToken) + { + var document = spanToTag.Document; + if (document == null) + return; + + var classified = await TryClassifyContainingMemberSpanAsync( + context, document, spanToTag.SnapshotSpan, classificationService, options, cancellationToken).ConfigureAwait(false); + if (classified) + { + return; + } + + // We weren't able to use our specialized codepaths for semantic classifying. + // Fall back to classifying the full span that was asked for. + await ClassifySpansAsync( + context, document, spanToTag.SnapshotSpan, classificationService, options, cancellationToken).ConfigureAwait(false); + } + + private async Task TryClassifyContainingMemberSpanAsync( + TaggerContext context, + Document document, + SnapshotSpan snapshotSpan, + IClassificationService classificationService, + ClassificationOptions options, + CancellationToken cancellationToken) + { + var range = context.TextChangeRange; + if (range == null) + { + // There was no text change range, we can't just reclassify a member body. + return false; + } + + // there was top level edit, check whether that edit updated top level element + if (!document.SupportsSyntaxTree) + return false; + + var lastSemanticVersion = (VersionStamp?)context.State; + if (lastSemanticVersion != null) + { + var currentSemanticVersion = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); + if (lastSemanticVersion.Value != currentSemanticVersion) + { + // A top level change was made. We can't perform this optimization. + return false; + } + } + + var service = document.GetRequiredLanguageService(); + + // perf optimization. Check whether all edits since the last update has happened within + // a member. If it did, it will find the member that contains the changes and only refresh + // that member. If possible, try to get a speculative binder to make things even cheaper. + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var changedSpan = new TextSpan(range.Value.Span.Start, range.Value.NewLength); + var member = service.GetContainingMemberDeclaration(root, changedSpan.Start); + if (member == null || !member.FullSpan.Contains(changedSpan)) + { + // The edit was not fully contained in a member. Reclassify everything. + return false; + } + + var memberBodySpan = service.GetMemberBodySpanForSpeculativeBinding(member); + if (memberBodySpan.IsEmpty) + { + // Wasn't a member we could reclassify independently. + return false; + } + + // TODO(cyrusn): Unclear what this logic is for. It looks like it's just trying to narrow the span down + // slightly from the full member, just to its body. Unclear if this provides any substantive benefits. But + // keeping for now to preserve long standing logic. + var memberSpanToClassify = memberBodySpan.Contains(changedSpan) + ? memberBodySpan.ToSpan() + : member.FullSpan.ToSpan(); + + // Take the subspan we know we want to classify, and intersect that with the actual span being asked for. + // That way if we're only asking for a portion of a method, we still only classify that, and not the whole + // method. + var finalSpanToClassify = memberSpanToClassify.Intersection(snapshotSpan.Span); + if (finalSpanToClassify is null) + return false; + + var subSpanToTag = new SnapshotSpan(snapshotSpan.Snapshot, finalSpanToClassify.Value); + + // re-classify only the member we're inside. + await ClassifySpansAsync( + context, document, subSpanToTag, classificationService, options, cancellationToken).ConfigureAwait(false); + return true; + } + + private async Task ClassifySpansAsync( + TaggerContext context, + Document document, + SnapshotSpan snapshotSpan, + IClassificationService classificationService, + ClassificationOptions options, + CancellationToken cancellationToken) + { + try + { + using (Logger.LogBlock(FunctionId.Tagger_SemanticClassification_TagProducer_ProduceTags, cancellationToken)) + { + using var _ = Classifier.GetPooledList(out var classifiedSpans); + + // Ensure that if we're producing tags for frozen/partial documents, that we pass along that info so + // that we preserve that same behavior in OOP if we end up computing the tags there. + options = options with { FrozenPartialSemantics = context.FrozenPartialSemantics }; + + if (_type == ClassificationType.Semantic) + { + await classificationService.AddSemanticClassificationsAsync( + document, snapshotSpan.Span.ToTextSpan(), options, classifiedSpans, cancellationToken).ConfigureAwait(false); + } + else if (_type == ClassificationType.EmbeddedLanguage) + { + await classificationService.AddEmbeddedLanguageClassificationsAsync( + document, snapshotSpan.Span.ToTextSpan(), options, classifiedSpans, cancellationToken).ConfigureAwait(false); + } + else + { + throw ExceptionUtilities.UnexpectedValue(_type); + } + + foreach (var span in classifiedSpans) + context.AddTag(ClassificationUtilities.Convert(_typeMap, snapshotSpan.Snapshot, span)); + + var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); + + // Let the context know that this was the span we actually tried to tag. + context.SetSpansTagged([snapshotSpan]); + context.State = version; + } + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) + { + throw ExceptionUtilities.Unreachable(); + } + } } diff --git a/src/EditorFeatures/Core/Classification/Semantic/ClassificationUtilities.cs b/src/EditorFeatures/Core/Classification/Semantic/ClassificationUtilities.cs index 98aac8074ef6a..c2f2c54f32c78 100644 --- a/src/EditorFeatures/Core/Classification/Semantic/ClassificationUtilities.cs +++ b/src/EditorFeatures/Core/Classification/Semantic/ClassificationUtilities.cs @@ -2,28 +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. -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.Collections; -using Microsoft.CodeAnalysis.Editor; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Editor.Tagging; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Classification; @@ -35,180 +17,4 @@ public static TagSpan Convert(IClassificationTypeMap typeMap classifiedSpan.TextSpan.ToSnapshotSpan(snapshot), new ClassificationTag(typeMap.GetClassificationType(classifiedSpan.ClassificationType))); } - - public static async Task ProduceTagsAsync( - TaggerContext context, - DocumentSnapshotSpan spanToTag, - IClassificationService classificationService, - ClassificationTypeMap typeMap, - ClassificationOptions options, - ClassificationType type, - CancellationToken cancellationToken) - { - var document = spanToTag.Document; - if (document == null) - return; - - // Don't block getting classifications on building the full compilation. This may take a significant amount - // of time and can cause a very latency sensitive operation (copying) to block the user while we wait on this - // work to happen. - // - // It's also a better experience to get classifications to the user faster versus waiting a potentially - // large amount of time waiting for all the compilation information to be built. For example, we can - // classify types that we've parsed in other files, or partially loaded from metadata, even if we're still - // parsing/loading. For cross language projects, this also produces semantic classifications more quickly - // as we do not have to wait on skeletons to be built. - - document = document.WithFrozenPartialSemantics(cancellationToken); - options = options with { ForceFrozenPartialSemanticsForCrossProcessOperations = true }; - - var classified = await TryClassifyContainingMemberSpanAsync( - context, document, spanToTag.SnapshotSpan, classificationService, typeMap, options, type, cancellationToken).ConfigureAwait(false); - if (classified) - { - return; - } - - // We weren't able to use our specialized codepaths for semantic classifying. - // Fall back to classifying the full span that was asked for. - await ClassifySpansAsync( - context, document, spanToTag.SnapshotSpan, classificationService, typeMap, options, type, cancellationToken).ConfigureAwait(false); - } - - private static async Task TryClassifyContainingMemberSpanAsync( - TaggerContext context, - Document document, - SnapshotSpan snapshotSpan, - IClassificationService classificationService, - ClassificationTypeMap typeMap, - ClassificationOptions options, - ClassificationType type, - CancellationToken cancellationToken) - { - var range = context.TextChangeRange; - if (range == null) - { - // There was no text change range, we can't just reclassify a member body. - return false; - } - - // there was top level edit, check whether that edit updated top level element - if (!document.SupportsSyntaxTree) - return false; - - var lastSemanticVersion = (VersionStamp?)context.State; - if (lastSemanticVersion != null) - { - var currentSemanticVersion = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - if (lastSemanticVersion.Value != currentSemanticVersion) - { - // A top level change was made. We can't perform this optimization. - return false; - } - } - - var service = document.GetRequiredLanguageService(); - - // perf optimization. Check whether all edits since the last update has happened within - // a member. If it did, it will find the member that contains the changes and only refresh - // that member. If possible, try to get a speculative binder to make things even cheaper. - - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var changedSpan = new TextSpan(range.Value.Span.Start, range.Value.NewLength); - var member = service.GetContainingMemberDeclaration(root, changedSpan.Start); - if (member == null || !member.FullSpan.Contains(changedSpan)) - { - // The edit was not fully contained in a member. Reclassify everything. - return false; - } - - var memberBodySpan = service.GetMemberBodySpanForSpeculativeBinding(member); - if (memberBodySpan.IsEmpty) - { - // Wasn't a member we could reclassify independently. - return false; - } - - // TODO(cyrusn): Unclear what this logic is for. It looks like it's just trying to narrow the span down - // slightly from the full member, just to its body. Unclear if this provides any substantive benefits. But - // keeping for now to preserve long standing logic. - var memberSpanToClassify = memberBodySpan.Contains(changedSpan) - ? memberBodySpan.ToSpan() - : member.FullSpan.ToSpan(); - - // Take the subspan we know we want to classify, and intersect that with the actual span being asked for. - // That way if we're only asking for a portion of a method, we still only classify that, and not the whole - // method. - var finalSpanToClassify = memberSpanToClassify.Intersection(snapshotSpan.Span); - if (finalSpanToClassify is null) - return false; - - var subSpanToTag = new SnapshotSpan(snapshotSpan.Snapshot, finalSpanToClassify.Value); - - // re-classify only the member we're inside. - await ClassifySpansAsync( - context, document, subSpanToTag, classificationService, typeMap, options, type, cancellationToken).ConfigureAwait(false); - return true; - } - - private static async Task ClassifySpansAsync( - TaggerContext context, - Document document, - SnapshotSpan snapshotSpan, - IClassificationService classificationService, - ClassificationTypeMap typeMap, - ClassificationOptions options, - ClassificationType type, - CancellationToken cancellationToken) - { - try - { - using (Logger.LogBlock(FunctionId.Tagger_SemanticClassification_TagProducer_ProduceTags, cancellationToken)) - { - using var _ = Classifier.GetPooledList(out var classifiedSpans); - - await AddClassificationsAsync( - classificationService, options, document, snapshotSpan, classifiedSpans, type, cancellationToken).ConfigureAwait(false); - - foreach (var span in classifiedSpans) - context.AddTag(Convert(typeMap, snapshotSpan.Snapshot, span)); - - var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - - // Let the context know that this was the span we actually tried to tag. - context.SetSpansTagged([snapshotSpan]); - context.State = version; - } - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) - { - throw ExceptionUtilities.Unreachable(); - } - } - - private static async Task AddClassificationsAsync( - IClassificationService classificationService, - ClassificationOptions options, - Document document, - SnapshotSpan snapshotSpan, - SegmentedList classifiedSpans, - ClassificationType type, - CancellationToken cancellationToken) - { - if (type == ClassificationType.Semantic) - { - await classificationService.AddSemanticClassificationsAsync( - document, snapshotSpan.Span.ToTextSpan(), options, classifiedSpans, cancellationToken).ConfigureAwait(false); - } - else if (type == ClassificationType.EmbeddedLanguage) - { - await classificationService.AddEmbeddedLanguageClassificationsAsync( - document, snapshotSpan.Span.ToTextSpan(), options, classifiedSpans, cancellationToken).ConfigureAwait(false); - } - else - { - throw ExceptionUtilities.UnexpectedValue(type); - } - } } diff --git a/src/EditorFeatures/Core/Classification/TotalClassificationTaggerProvider.cs b/src/EditorFeatures/Core/Classification/TotalClassificationTaggerProvider.cs index ed3aab5395493..89800c1090014 100644 --- a/src/EditorFeatures/Core/Classification/TotalClassificationTaggerProvider.cs +++ b/src/EditorFeatures/Core/Classification/TotalClassificationTaggerProvider.cs @@ -3,10 +3,10 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; @@ -21,6 +21,7 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Classification; @@ -68,166 +69,207 @@ internal sealed class TotalClassificationTaggerProvider( return new TotalClassificationAggregateTagger(syntacticTagger, semanticTagger, embeddedTagger); } +} + +internal sealed class TotalClassificationAggregateTagger( + EfficientTagger syntacticTagger, + EfficientTagger semanticTagger, + EfficientTagger embeddedTagger) + : AbstractAggregateTagger([syntacticTagger, semanticTagger, embeddedTagger]) +{ + private static readonly Comparison> s_spanComparison = static (s1, s2) => s1.Span.Start - s2.Span.Start; - internal sealed class TotalClassificationAggregateTagger( - EfficientTagger syntacticTagger, - EfficientTagger semanticTagger, - EfficientTagger embeddedTagger) - : AbstractAggregateTagger(ImmutableArray.Create(syntacticTagger, semanticTagger, embeddedTagger)) + public override void AddTags(NormalizedSnapshotSpanCollection spans, SegmentedList> totalTags) { - private static readonly Comparison> s_spanComparison = static (s1, s2) => s1.Span.Start - s2.Span.Start; + // Everything we pass in is synchronous, so we should immediately get a completed task back out. + AddTagsAsync( + spans, + totalTags, + addSyntacticSpansAsync: static (spans, tags, arg) => + { + arg.syntacticTagger.AddTags(spans, tags); + return Task.CompletedTask; + }, + addSemanticSpansAsync: static (spans, tags, arg) => + { + arg.semanticTagger.AddTags(spans, tags); + return Task.CompletedTask; + }, + addEmbeddedSpansAsync: static (spans, tags, arg) => + { + arg.embeddedTagger.AddTags(spans, tags); + return Task.CompletedTask; + }, + (syntacticTagger, semanticTagger, embeddedTagger)).VerifyCompleted(); + } - public override void AddTags(NormalizedSnapshotSpanCollection spans, SegmentedList> totalTags) - { - // First, get all the syntactic tags. While they are generally overridden by semantic tags (since semantics - // allows us to understand better what things like identifiers mean), they do take precedence for certain - // tags like 'Comments' and 'Excluded Code'. In those cases we want the classification to 'snap' instantly to - // the syntactic state, and we do not want things like semantic classifications showing up over that. + public static async Task AddTagsAsync( + NormalizedSnapshotSpanCollection spans, + SegmentedList> totalTags, + Func>, TArg, Task> addSyntacticSpansAsync, + Func>, TArg, Task> addSemanticSpansAsync, + Func>, TArg, Task> addEmbeddedSpansAsync, + TArg arg) + { + // First, get all the syntactic tags. While they are generally overridden by semantic tags (since semantics + // allows us to understand better what things like identifiers mean), they do take precedence for certain + // tags like 'Comments' and 'Excluded Code'. In those cases we want the classification to 'snap' instantly to + // the syntactic state, and we do not want things like semantic classifications showing up over that. - using var _1 = SegmentedListPool.GetPooledList>(out var stringLiterals); - using var _2 = SegmentedListPool.GetPooledList>(out var syntacticSpans); - using var _3 = SegmentedListPool.GetPooledList>(out var semanticSpans); + using var _1 = SegmentedListPool.GetPooledList>(out var stringLiterals); + using var _2 = SegmentedListPool.GetPooledList>(out var syntacticSpans); + using var _3 = SegmentedListPool.GetPooledList>(out var semanticSpans); - syntacticTagger.AddTags(spans, syntacticSpans); - semanticTagger.AddTags(spans, semanticSpans); + await addSyntacticSpansAsync(spans, syntacticSpans, arg).ConfigureAwait(false); + await addSemanticSpansAsync(spans, semanticSpans, arg).ConfigureAwait(false); - syntacticSpans.Sort(s_spanComparison); - semanticSpans.Sort(s_spanComparison); + syntacticSpans.Sort(s_spanComparison); + semanticSpans.Sort(s_spanComparison); - using var syntacticEnumerator = syntacticSpans.GetEnumerator(); - using var semanticEnumerator = semanticSpans.GetEnumerator(); + using var syntacticEnumerator = syntacticSpans.GetEnumerator(); + using var semanticEnumerator = semanticSpans.GetEnumerator(); - var currentSyntactic = GetNextSyntacticSpan(); - var currentSemantic = GetNextSemanticSpan(); + var currentSyntactic = GetNextSyntacticSpan(); + var currentSemantic = GetNextSemanticSpan(); - while (currentSyntactic != null && currentSemantic != null) + while (currentSyntactic != null && currentSemantic != null) + { + // If both the syntactic and semantic tags are for the same span, and the semantic tag is more specific, + // then just prefer that one (and eschew the syntactic one). Semantics is more accurate, but often will + // produce these accurate tags more slowly than the syntactic classifier. This allows the syntactic + // classifier to produce an initial result, which the semantic classifier can refine. + if (currentSyntactic.Span == currentSemantic.Span && + currentSemantic.Tag.ClassificationType.IsOfType(currentSyntactic.Tag.ClassificationType.Classification)) { - // as long as we see semantic spans before the next syntactic one, keep adding them. - if (currentSemantic.Span.Start <= currentSyntactic.Span.Start) - { - totalTags.Add(currentSemantic); - currentSemantic = GetNextSemanticSpan(); - } - else - { - // We're on a syntactic span before the next semantic one. - - // If it's a comment or excluded code, then we want to ignore every semantic classification that - // potentially overlaps with it so that semantic classifications don't show up *on top of* them. We - // want commenting out code to feel like' it instantly snaps to that state. - if (TryProcessCommentOrExcludedCode()) - continue; - - // If we have a string literal of some sort add it to the list to be processed later. We'll want to - // compute embedded classifications for them, and have those classifications override the string - // literals. - if (TryProcessSyntacticStringLiteral()) - continue; - - // Normal case. Just add the syntactic span and continue. - totalTags.Add(currentSyntactic); - currentSyntactic = GetNextSyntacticSpan(); - } + totalTags.Add(currentSemantic); + currentSyntactic = GetNextSyntacticSpan(); + currentSemantic = GetNextSemanticSpan(); } - - // Add any remaining semantic spans following the syntactic ones. - while (currentSemantic != null) + else if (currentSemantic.Span.Start <= currentSyntactic.Span.Start) { + // as long as we see semantic spans before the next syntactic one, keep adding them. totalTags.Add(currentSemantic); currentSemantic = GetNextSemanticSpan(); } - - // Add any remaining syntactic spans following the semantic ones. - while (currentSyntactic != null) + else { - // don't have to worry about comments/excluded code since there are no semantic tags we want to override. + // We're on a syntactic span before the next semantic one. + + // If it's a comment or excluded code, then we want to ignore every semantic classification that + // potentially overlaps with it so that semantic classifications don't show up *on top of* them. We + // want commenting out code to feel like' it instantly snaps to that state. + if (TryProcessCommentOrExcludedCode()) + continue; + + // If we have a string literal of some sort add it to the list to be processed later. We'll want to + // compute embedded classifications for them, and have those classifications override the string + // literals. if (TryProcessSyntacticStringLiteral()) continue; + // Normal case. Just add the syntactic span and continue. totalTags.Add(currentSyntactic); currentSyntactic = GetNextSyntacticSpan(); } + } - // We've added almost all the syntactic and semantic tags (properly skipping any semantic tags that are - // overridden by comments or excluded code). All that remains is adding back the string literals we - // skipped. However, when we do so, we'll see if those string literals themselves should be overridden - // by any embedded classifications. - AddEmbeddedClassifications(); + // Add any remaining semantic spans following the syntactic ones. + while (currentSemantic != null) + { + totalTags.Add(currentSemantic); + currentSemantic = GetNextSemanticSpan(); + } - return; + // Add any remaining syntactic spans following the semantic ones. + while (currentSyntactic != null) + { + // don't have to worry about comments/excluded code since there are no semantic tags we want to override. + if (TryProcessSyntacticStringLiteral()) + continue; - bool TryProcessSyntacticStringLiteral() - { - if (currentSyntactic.Tag.ClassificationType.Classification is not ClassificationTypeNames.StringLiteral and not ClassificationTypeNames.VerbatimStringLiteral) - return false; + totalTags.Add(currentSyntactic); + currentSyntactic = GetNextSyntacticSpan(); + } - stringLiterals.Add(currentSyntactic); - currentSyntactic = GetNextSyntacticSpan(); - return true; - } + // We've added almost all the syntactic and semantic tags (properly skipping any semantic tags that are + // overridden by comments or excluded code). All that remains is adding back the string literals we + // skipped. However, when we do so, we'll see if those string literals themselves should be overridden + // by any embedded classifications. + await AddEmbeddedClassificationsAsync().ConfigureAwait(false); - bool TryProcessCommentOrExcludedCode() - { - if (currentSyntactic.Tag.ClassificationType.Classification is not ClassificationTypeNames.Comment and not ClassificationTypeNames.ExcludedCode) - return false; + return; - // Keep skipping semantic tags that overlaps with this syntactic tag. - while (currentSemantic != null && currentSemantic.Span.OverlapsWith(currentSyntactic.Span.Span)) - currentSemantic = GetNextSemanticSpan(); + bool TryProcessSyntacticStringLiteral() + { + if (currentSyntactic.Tag.ClassificationType.Classification is not ClassificationTypeNames.StringLiteral and not ClassificationTypeNames.VerbatimStringLiteral) + return false; - // now add that syntactic span. - totalTags.Add(currentSyntactic); - currentSyntactic = GetNextSyntacticSpan(); - return true; - } + stringLiterals.Add(currentSyntactic); + currentSyntactic = GetNextSyntacticSpan(); + return true; + } + + bool TryProcessCommentOrExcludedCode() + { + if (currentSyntactic.Tag.ClassificationType.Classification is not ClassificationTypeNames.Comment and not ClassificationTypeNames.ExcludedCode) + return false; + + // Keep skipping semantic tags that overlaps with this syntactic tag. + while (currentSemantic != null && currentSemantic.Span.OverlapsWith(currentSyntactic.Span.Span)) + currentSemantic = GetNextSemanticSpan(); + + // now add that syntactic span. + totalTags.Add(currentSyntactic); + currentSyntactic = GetNextSyntacticSpan(); + return true; + } + + async Task AddEmbeddedClassificationsAsync() + { + // nothing to do if we didn't run into any string literals. + if (stringLiterals.Count == 0) + return; + + // Only need to ask for the spans that overlapped the string literals. + using var _1 = SegmentedListPool.GetPooledList>(out var embeddedClassifications); - void AddEmbeddedClassifications() + var stringLiteralSpansFull = new NormalizedSnapshotSpanCollection(stringLiterals.Select(s => s.Span)); + + // The spans of the string literal itself may be far off screen. Intersect the string literal spans + // with the view spans to get the actual spans we want to classify. + var stringLiteralSpans = NormalizedSnapshotSpanCollection.Intersection(stringLiteralSpansFull, spans); + + await addEmbeddedSpansAsync(stringLiteralSpans, embeddedClassifications, arg).ConfigureAwait(false); + + // Nothing complex to do if we got no embedded classifications back. Just add in all the string + // classifications, untouched. + if (embeddedClassifications.Count == 0) { - // nothing to do if we didn't run into any string literals. - if (stringLiterals.Count == 0) - return; - - // Only need to ask for the spans that overlapped the string literals. - using var _1 = SegmentedListPool.GetPooledList>(out var embeddedClassifications); - - var stringLiteralSpansFull = new NormalizedSnapshotSpanCollection(stringLiterals.Select(s => s.Span)); - - // The spans of the string literal itself may be far off screen. Intersect the string literal spans - // with the view spans to get the actual spans we want to classify. - var stringLiteralSpans = NormalizedSnapshotSpanCollection.Intersection(stringLiteralSpansFull, spans); - - embeddedTagger.AddTags(stringLiteralSpans, embeddedClassifications); - - // Nothing complex to do if we got no embedded classifications back. Just add in all the string - // classifications, untouched. - if (embeddedClassifications.Count == 0) - { - totalTags.AddRange(stringLiterals); - return; - } - - // ClassifierHelper.MergeParts requires these to be sorted. - stringLiterals.Sort(s_spanComparison); - embeddedClassifications.Sort(s_spanComparison); - - // Call into the helper to merge the string literals and embedded classifications into the final result. - // The helper will add all the embedded classifications first, then add string literal classifications - // in the the space between the embedded classifications that were originally classified as a string - // literal. - ClassifierHelper.MergeParts, ClassificationTagSpanIntervalIntrospector>( - stringLiterals, - embeddedClassifications, - totalTags, - static tag => tag.Span.Span.ToTextSpan(), - static (original, final) => new TagSpan(new SnapshotSpan(original.Span.Snapshot, final.ToSpan()), original.Tag)); + totalTags.AddRange(stringLiterals); + return; } - ITagSpan? GetNextSyntacticSpan() - => syntacticEnumerator.MoveNext() ? syntacticEnumerator.Current : null; - - ITagSpan? GetNextSemanticSpan() - => semanticEnumerator.MoveNext() ? semanticEnumerator.Current : null; + // ClassifierHelper.MergeParts requires these to be sorted. + stringLiterals.Sort(s_spanComparison); + embeddedClassifications.Sort(s_spanComparison); + + // Call into the helper to merge the string literals and embedded classifications into the final result. + // The helper will add all the embedded classifications first, then add string literal classifications + // in the the space between the embedded classifications that were originally classified as a string + // literal. + ClassifierHelper.MergeParts, ClassificationTagSpanIntervalIntrospector>( + stringLiterals, + embeddedClassifications, + totalTags, + static tag => tag.Span.Span.ToTextSpan(), + static (original, final) => new TagSpan(new SnapshotSpan(original.Span.Snapshot, final.ToSpan()), original.Tag)); } + + ITagSpan? GetNextSyntacticSpan() + => syntacticEnumerator.MoveNext() ? syntacticEnumerator.Current : null; + + ITagSpan? GetNextSemanticSpan() + => semanticEnumerator.MoveNext() ? semanticEnumerator.Current : null; } private readonly struct ClassificationTagSpanIntervalIntrospector : IIntervalIntrospector> diff --git a/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs b/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs index 532504fbe00cf..ae249a9df14df 100644 --- a/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs +++ b/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs @@ -171,7 +171,7 @@ internal async Task> GetContextFrom } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } // We didn't have regular source references, but possibly: diff --git a/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs b/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs index c8c5859d075d5..f025bbca1dd3d 100644 --- a/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs +++ b/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs @@ -436,7 +436,7 @@ private ImmutableArray GetUncommentedSpansInSelection() } } - return uncommentedSpans.ToImmutableArray(); + return [.. uncommentedSpans]; } } } diff --git a/src/EditorFeatures/Core/Copilot/CopilotTaggerProvider.cs b/src/EditorFeatures/Core/Copilot/CopilotTaggerProvider.cs index 1ec2d03fbfd0b..9647385753adc 100644 --- a/src/EditorFeatures/Core/Copilot/CopilotTaggerProvider.cs +++ b/src/EditorFeatures/Core/Copilot/CopilotTaggerProvider.cs @@ -64,28 +64,29 @@ protected override IEnumerable GetSpansToTag(ITextView? textView, Contract.ThrowIfNull(textView); // We only care about the cases where we have caret. - if (textView.GetCaretPoint(subjectBuffer) is { } caret) - return SpecializedCollections.SingletonEnumerable(new SnapshotSpan(caret, 0)); - - return SpecializedCollections.EmptyEnumerable(); + return textView.GetCaretPoint(subjectBuffer) is { } caret ? [new SnapshotSpan(caret, 0)] : []; } protected override async Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition, CancellationToken cancellationToken) { if (spanToTag.Document is not { } document - || document.GetLanguageService() is not { } service - || !await service.IsCodeAnalysisOptionEnabledAsync().ConfigureAwait(false)) + || document.GetLanguageService() is not { } optionsService + || await optionsService.IsCodeAnalysisOptionEnabledAsync().ConfigureAwait(false) is false) + { + return; + } + + if (document.GetLanguageService() is not { } analysisService) { return; } // Fetch the Copilot code analysis prompt titles, each title can define a separate code analysis prompt. // Currently, we only support running the primary (first) code analysis prompt. - var prompts = await service.GetAvailablePromptTitlesAsync(document, cancellationToken).ConfigureAwait(false); + var prompts = await analysisService.GetAvailablePromptTitlesAsync(document, cancellationToken).ConfigureAwait(false); if (prompts.Length > 0) { - // Invoke analysis call into the Copilot service for the containing method's span. - await service.AnalyzeDocumentAsync(document, new(spanToTag.SnapshotSpan.Start, 0), prompts[0], cancellationToken).ConfigureAwait(false); + await analysisService.AnalyzeDocumentAsync(document, spanToTag.SnapshotSpan.Span.ToTextSpan(), prompts[0], cancellationToken).ConfigureAwait(false); } } } diff --git a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingService.cs b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingService.cs index 3ba759484c71e..1109908ec0739 100644 --- a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingService.cs +++ b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingService.cs @@ -38,7 +38,7 @@ internal sealed class ActiveStatementTrackingService(Workspace workspace, IAsync [ExportWorkspaceServiceFactory(typeof(IActiveStatementTrackingService), ServiceLayer.Editor), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal sealed class Factory(IAsynchronousOperationListenerProvider listenerProvider) : IWorkspaceServiceFactory + internal sealed class ServiceFactory(IAsynchronousOperationListenerProvider listenerProvider) : IWorkspaceServiceFactory { private readonly IAsynchronousOperationListenerProvider _listenerProvider = listenerProvider; @@ -46,6 +46,15 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) => new ActiveStatementTrackingService(workspaceServices.Workspace, _listenerProvider.GetListener(FeatureAttribute.EditAndContinue)); } + [ExportWorkspaceService(typeof(IActiveStatementSpanLocator), ServiceLayer.Editor), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class SpanLocator() : IActiveStatementSpanLocator + { + public ValueTask> GetSpansAsync(Solution solution, DocumentId? documentId, string filePath, CancellationToken cancellationToken) + => solution.Services.GetRequiredService().GetSpansAsync(solution, documentId, filePath, cancellationToken); + } + private readonly IAsynchronousOperationListener _listener = listener; private TrackingSession? _session; @@ -56,7 +65,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) /// public event Action? TrackingChanged; - public void StartTracking(Solution solution, IActiveStatementSpanProvider spanProvider) + public void StartTracking(Solution solution, IActiveStatementSpanFactory spanProvider) { var newSession = new TrackingSession(_workspace, spanProvider); if (Interlocked.CompareExchange(ref _session, newSession, null) != null) @@ -91,7 +100,7 @@ internal sealed class TrackingSession { private readonly Workspace _workspace; private readonly CancellationTokenSource _cancellationSource = new(); - private readonly IActiveStatementSpanProvider _spanProvider; + private readonly IActiveStatementSpanFactory _spanProvider; private readonly ICompileTimeSolutionProvider _compileTimeSolutionProvider; #region lock(_trackingSpans) @@ -105,7 +114,7 @@ internal sealed class TrackingSession #endregion - public TrackingSession(Workspace workspace, IActiveStatementSpanProvider spanProvider) + public TrackingSession(Workspace workspace, IActiveStatementSpanFactory spanProvider) { _workspace = workspace; _spanProvider = spanProvider; @@ -259,7 +268,7 @@ private static ImmutableArray UpdateTrackingSpans( var newSpan = newSpans[i]; Contract.ThrowIfFalse(oldSpan.Flags == newSpan.Flags); - Contract.ThrowIfFalse(oldSpan.Ordinal == newSpan.Ordinal); + Contract.ThrowIfFalse(oldSpan.Id == newSpan.Id); var newTextSpan = snapshot.GetTextSpan(newSpan.LineSpan).ToSpan(); if (oldSpan.Span.GetSpan(snapshot).Span != newTextSpan) @@ -272,7 +281,7 @@ private static ImmutableArray UpdateTrackingSpans( lazyBuilder[i] = new ActiveStatementTrackingSpan( snapshot.CreateTrackingSpan(newTextSpan, SpanTrackingMode.EdgeExclusive), - newSpan.Ordinal, + newSpan.Id, newSpan.Flags, newSpan.UnmappedDocumentId); } @@ -316,7 +325,7 @@ public async ValueTask> GetSpansAsync(Soluti var snapshot = sourceText.FindCorrespondingEditorTextSnapshot(); if (snapshot != null && snapshot.TextBuffer == documentSpans.First().Span.TextBuffer) { - return documentSpans.SelectAsArray(s => new ActiveStatementSpan(s.Ordinal, s.Span.GetSpan(snapshot).ToLinePositionSpan(), s.Flags, s.UnmappedDocumentId)); + return documentSpans.SelectAsArray(s => new ActiveStatementSpan(s.Id, s.Span.GetSpan(snapshot).ToLinePositionSpan(), s.Flags, s.UnmappedDocumentId)); } } } diff --git a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingSpan.cs b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingSpan.cs index 4c950bf180af3..5aec5a7c951c8 100644 --- a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingSpan.cs +++ b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingSpan.cs @@ -8,10 +8,10 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; -internal readonly struct ActiveStatementTrackingSpan(ITrackingSpan trackingSpan, int ordinal, ActiveStatementFlags flags, DocumentId? unmappedDocumentId) +internal readonly struct ActiveStatementTrackingSpan(ITrackingSpan trackingSpan, ActiveStatementId id, ActiveStatementFlags flags, DocumentId? unmappedDocumentId) { public readonly ITrackingSpan Span = trackingSpan; - public readonly int Ordinal = ordinal; + public readonly ActiveStatementId Id = id; public readonly ActiveStatementFlags Flags = flags; public readonly DocumentId? UnmappedDocumentId = unmappedDocumentId; @@ -21,5 +21,5 @@ internal readonly struct ActiveStatementTrackingSpan(ITrackingSpan trackingSpan, public bool IsLeaf => (Flags & ActiveStatementFlags.LeafFrame) != 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); + => new(snapshot.CreateTrackingSpan(snapshot.GetTextSpan(span.LineSpan).ToSpan(), SpanTrackingMode.EdgeExclusive), span.Id, span.Flags, span.UnmappedDocumentId); } diff --git a/src/EditorFeatures/Core/EditAndContinue/DebuggerContractVersionCheck.cs b/src/EditorFeatures/Core/EditAndContinue/DebuggerContractVersionCheck.cs deleted file mode 100644 index 78b8b9e65ebf9..0000000000000 --- a/src/EditorFeatures/Core/EditAndContinue/DebuggerContractVersionCheck.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.Runtime.CompilerServices; -using Microsoft.VisualStudio.Debugger.Contracts.HotReload; - -namespace Microsoft.CodeAnalysis.EditAndContinue; - -/// -/// Allow us to run integration tests on older VS than build that has the required version of Microsoft.VisualStudio.Debugger.Contracts. -/// -internal static class DebuggerContractVersionCheck -{ - public static bool IsRequiredDebuggerContractVersionAvailable() - { - try - { - _ = LoadContracts(); - return true; - } - catch - { - return false; - } - } - - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - private static Type LoadContracts() - => typeof(ManagedActiveStatementUpdate); -} diff --git a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs deleted file mode 100644 index 1508acb0ad807..0000000000000 --- a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs +++ /dev/null @@ -1,90 +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.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Debugging; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Roslyn.Utilities; -using Microsoft.VisualStudio.Debugger.Contracts; -using Microsoft.CodeAnalysis.Simplification; - -namespace Microsoft.CodeAnalysis.EditAndContinue; - -[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] -internal sealed class EditAndContinueDiagnosticAnalyzer : DocumentDiagnosticAnalyzer, IBuiltInAnalyzer -{ - private static readonly ImmutableArray s_supportedDiagnostics = EditAndContinueDiagnosticDescriptors.GetDescriptors(); - - // Return known descriptors. This will not include module diagnostics reported on behalf of the debugger. - public override ImmutableArray SupportedDiagnostics - => s_supportedDiagnostics; - - public DiagnosticAnalyzerCategory GetAnalyzerCategory() - => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; - - public bool IsHighPriority => false; - - public bool OpenFileOnly(SimplifierOptions? options) - => false; - - // No syntax diagnostics produced by the EnC engine. - public override Task> AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) - => SpecializedTasks.EmptyImmutableArray(); - - public override Task> AnalyzeSemanticsAsync(Document document, CancellationToken cancellationToken) - { - if (!DebuggerContractVersionCheck.IsRequiredDebuggerContractVersionAvailable()) - { - return SpecializedTasks.EmptyImmutableArray(); - } - - return AnalyzeSemanticsImplAsync(document, cancellationToken); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static async Task> AnalyzeSemanticsImplAsync(Document designTimeDocument, CancellationToken cancellationToken) - { - var workspace = designTimeDocument.Project.Solution.Workspace; - - if (workspace.Services.HostServices is not IMefHostExportProvider mefServices) - { - return []; - } - - // avoid creating and synchronizing compile-time solution if the Hot Reload/EnC session is not active - if (mefServices.GetExports().SingleOrDefault()?.Value.IsSessionActive != true) - { - return []; - } - - var designTimeSolution = designTimeDocument.Project.Solution; - var compileTimeSolution = workspace.Services.GetRequiredService().GetCompileTimeSolution(designTimeSolution); - - var compileTimeDocument = await CompileTimeSolutionProvider.TryGetCompileTimeDocumentAsync(designTimeDocument, compileTimeSolution, cancellationToken).ConfigureAwait(false); - if (compileTimeDocument == null) - { - return []; - } - - // EnC services should never be called on a design-time solution. - - var proxy = new RemoteEditAndContinueServiceProxy(workspace); - - var activeStatementSpanProvider = new ActiveStatementSpanProvider(async (documentId, filePath, cancellationToken) => - { - var trackingService = workspace.Services.GetRequiredService(); - return await trackingService.GetSpansAsync(compileTimeSolution, documentId, filePath, cancellationToken).ConfigureAwait(false); - }); - - return await proxy.GetDocumentDiagnosticsAsync(compileTimeDocument, designTimeDocument, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); - } -} diff --git a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs index 10a8aca886b7b..887e433c9882c 100644 --- a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs +++ b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.BrokeredServices; @@ -27,10 +28,9 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class EditAndContinueLanguageService( IServiceBrokerProvider serviceBrokerProvider, + EditAndContinueSessionState sessionState, Lazy workspaceProvider, Lazy debuggerService, - IDiagnosticAnalyzerService diagnosticService, - EditAndContinueDiagnosticUpdateSource diagnosticUpdateSource, PdbMatchingSourceTextProvider sourceTextProvider, IDiagnosticsRefresher diagnosticRefresher, IAsynchronousOperationListenerProvider listenerProvider) : IManagedHotReloadLanguageService, IEditAndContinueSolutionProvider @@ -45,18 +45,9 @@ public NoSessionException() } } - private readonly PdbMatchingSourceTextProvider _sourceTextProvider = sourceTextProvider; - private readonly IDiagnosticsRefresher _diagnosticRefresher = diagnosticRefresher; private readonly IAsynchronousOperationListener _asyncListener = listenerProvider.GetListener(FeatureAttribute.EditAndContinue); - private readonly Lazy _debuggerService = debuggerService; - private readonly IDiagnosticAnalyzerService _diagnosticService = diagnosticService; - private readonly EditAndContinueDiagnosticUpdateSource _diagnosticUpdateSource = diagnosticUpdateSource; private readonly HotReloadLoggerProxy _logger = new(serviceBrokerProvider.ServiceBroker); - public readonly Lazy WorkspaceProvider = workspaceProvider; - - public bool IsSessionActive { get; private set; } - private bool _disabled; private RemoteDebuggingSessionProxy? _debuggingSession; @@ -71,7 +62,7 @@ public void SetFileLoggingDirectory(string? logDirectory) { try { - var proxy = new RemoteEditAndContinueServiceProxy(WorkspaceProvider.Value.Workspace); + var proxy = new RemoteEditAndContinueServiceProxy(Services); await proxy.SetFileLoggingDirectoryAsync(logDirectory, CancellationToken.None).ConfigureAwait(false); } catch @@ -81,17 +72,20 @@ public void SetFileLoggingDirectory(string? logDirectory) }); } - private Solution GetCurrentCompileTimeSolution(Solution? currentDesignTimeSolution = null) - { - var workspace = WorkspaceProvider.Value.Workspace; - return workspace.Services.GetRequiredService().GetCompileTimeSolution(currentDesignTimeSolution ?? workspace.CurrentSolution); - } + private SolutionServices Services + => workspaceProvider.Value.Workspace.Services.SolutionServices; + + private Solution GetCurrentDesignTimeSolution() + => workspaceProvider.Value.Workspace.CurrentSolution; + + private Solution GetCurrentCompileTimeSolution(Solution currentDesignTimeSolution) + => Services.GetRequiredService().GetCompileTimeSolution(currentDesignTimeSolution); private RemoteDebuggingSessionProxy GetDebuggingSession() => _debuggingSession ?? throw new NoSessionException(); private IActiveStatementTrackingService GetActiveStatementTrackingService() - => WorkspaceProvider.Value.Workspace.Services.GetRequiredService(); + => Services.GetRequiredService(); internal void Disable(Exception e) { @@ -103,12 +97,18 @@ internal void Disable(Exception e) .ReportNonFatalErrorAsync().CompletesAsyncOperation(token); } + private void UpdateApplyChangesDiagnostics(ImmutableArray diagnostics) + { + sessionState.ApplyChangesDiagnostics = diagnostics; + diagnosticRefresher.RequestWorkspaceRefresh(); + } + /// /// Called by the debugger when a debugging session starts and managed debugging is being used. /// public async ValueTask StartSessionAsync(CancellationToken cancellationToken) { - IsSessionActive = true; + sessionState.IsSessionActive = true; if (_disabled) { @@ -119,21 +119,20 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken) { // Activate listener before capturing the current solution snapshot, // so that we don't miss any pertinent workspace update events. - _sourceTextProvider.Activate(); + sourceTextProvider.Activate(); - var workspace = WorkspaceProvider.Value.Workspace; - var currentSolution = workspace.CurrentSolution; + var currentSolution = GetCurrentDesignTimeSolution(); _committedDesignTimeSolution = currentSolution; var solution = GetCurrentCompileTimeSolution(currentSolution); - _sourceTextProvider.SetBaseline(currentSolution); + sourceTextProvider.SetBaseline(currentSolution); - var proxy = new RemoteEditAndContinueServiceProxy(workspace); + var proxy = new RemoteEditAndContinueServiceProxy(Services); _debuggingSession = await proxy.StartDebuggingSessionAsync( solution, - new ManagedHotReloadServiceBridge(_debuggerService.Value), - _sourceTextProvider, + new ManagedHotReloadServiceBridge(debuggerService.Value), + sourceTextProvider, captureMatchingDocuments: [], captureAllMatchingDocuments: false, reportDiagnostics: true, @@ -146,77 +145,52 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken) } } - public async ValueTask EnterBreakStateAsync(CancellationToken cancellationToken) - { - if (_disabled) - { - return; - } - - var solution = GetCurrentCompileTimeSolution(); - var session = GetDebuggingSession(); - - try - { - await session.BreakStateOrCapabilitiesChangedAsync(_diagnosticService, _diagnosticUpdateSource, inBreakState: true, cancellationToken).ConfigureAwait(false); - - _diagnosticRefresher.RequestWorkspaceRefresh(); - } - catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) - { - Disable(e); - return; - } + public ValueTask EnterBreakStateAsync(CancellationToken cancellationToken) + => BreakStateOrCapabilitiesChangedAsync(inBreakState: true, cancellationToken); - // Start tracking after we entered break state so that break-state session is active. - // This is potentially costly operation as source generators might get invoked in OOP - // to determine the spans of all active statements. - // We start the operation but do not wait for it to complete. - // The tracking session is cancelled when we exit the break state. + public ValueTask ExitBreakStateAsync(CancellationToken cancellationToken) + => BreakStateOrCapabilitiesChangedAsync(inBreakState: false, cancellationToken); - GetActiveStatementTrackingService().StartTracking(solution, session); - } + public ValueTask OnCapabilitiesChangedAsync(CancellationToken cancellationToken) + => BreakStateOrCapabilitiesChangedAsync(inBreakState: null, cancellationToken); - public async ValueTask ExitBreakStateAsync(CancellationToken cancellationToken) + private async ValueTask BreakStateOrCapabilitiesChangedAsync(bool? inBreakState, CancellationToken cancellationToken) { if (_disabled) { return; } - var session = GetDebuggingSession(); - try { - await session.BreakStateOrCapabilitiesChangedAsync(_diagnosticService, _diagnosticUpdateSource, inBreakState: false, cancellationToken).ConfigureAwait(false); + var session = GetDebuggingSession(); + var solution = (inBreakState == true) ? GetCurrentCompileTimeSolution(GetCurrentDesignTimeSolution()) : null; - _diagnosticRefresher.RequestWorkspaceRefresh(); - GetActiveStatementTrackingService().EndTracking(); - } - catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) - { - Disable(e); - return; - } - } + await session.BreakStateOrCapabilitiesChangedAsync(inBreakState, cancellationToken).ConfigureAwait(false); - public async ValueTask OnCapabilitiesChangedAsync(CancellationToken cancellationToken) - { - if (_disabled) - { - return; - } - - try - { - await GetDebuggingSession().BreakStateOrCapabilitiesChangedAsync(_diagnosticService, _diagnosticUpdateSource, inBreakState: null, cancellationToken).ConfigureAwait(false); - - _diagnosticRefresher.RequestWorkspaceRefresh(); + if (inBreakState == false) + { + GetActiveStatementTrackingService().EndTracking(); + } + else if (inBreakState == true) + { + // Start tracking after we entered break state so that break-state session is active. + // This is potentially costly operation as source generators might get invoked in OOP + // to determine the spans of all active statements. + // We start the operation but do not wait for it to complete. + // The tracking session is cancelled when we exit the break state. + + Debug.Assert(solution != null); + GetActiveStatementTrackingService().StartTracking(solution, session); + } } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { Disable(e); } + + // clear diagnostics reported previously: + UpdateApplyChangesDiagnostics([]); } public async ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) @@ -241,7 +215,7 @@ public async ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) try { - await GetDebuggingSession().CommitSolutionUpdateAsync(_diagnosticService, cancellationToken).ConfigureAwait(false); + await GetDebuggingSession().CommitSolutionUpdateAsync(cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -268,16 +242,13 @@ public async ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) public async ValueTask EndSessionAsync(CancellationToken cancellationToken) { - IsSessionActive = false; + sessionState.IsSessionActive = false; if (!_disabled) { try { - var solution = GetCurrentCompileTimeSolution(); - await GetDebuggingSession().EndDebuggingSessionAsync(solution, _diagnosticUpdateSource, _diagnosticService, cancellationToken).ConfigureAwait(false); - - _diagnosticRefresher.RequestWorkspaceRefresh(); + await GetDebuggingSession().EndDebuggingSessionAsync(cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -285,7 +256,10 @@ public async ValueTask EndSessionAsync(CancellationToken cancellationToken) } } - _sourceTextProvider.Deactivate(); + // clear diagnostics reported previously: + UpdateApplyChangesDiagnostics([]); + + sourceTextProvider.Deactivate(); _debuggingSession = null; _committedDesignTimeSolution = null; _pendingUpdatedDesignTimeSolution = null; @@ -321,7 +295,7 @@ public async ValueTask HasChangesAsync(string? sourceFilePath, Cancellatio Contract.ThrowIfNull(_committedDesignTimeSolution); var oldSolution = _committedDesignTimeSolution; - var newSolution = WorkspaceProvider.Value.Workspace.CurrentSolution; + var newSolution = GetCurrentDesignTimeSolution(); return (sourceFilePath != null) ? await EditSession.HasChangesAsync(oldSolution, newSolution, sourceFilePath, cancellationToken).ConfigureAwait(false) @@ -340,11 +314,10 @@ public async ValueTask GetUpdatesAsync(CancellationToke return new ManagedHotReloadUpdates([], []); } - var workspace = WorkspaceProvider.Value.Workspace; - var designTimeSolution = workspace.CurrentSolution; + var designTimeSolution = GetCurrentDesignTimeSolution(); var solution = GetCurrentCompileTimeSolution(designTimeSolution); var activeStatementSpanProvider = GetActiveStatementSpanProvider(solution); - var (moduleUpdates, diagnosticData, rudeEdits, syntaxError) = await GetDebuggingSession().EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); + var (moduleUpdates, diagnosticData, rudeEdits, syntaxError) = await GetDebuggingSession().EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); // Only store the solution if we have any changes to apply, otherwise CommitUpdatesAsync/DiscardUpdatesAsync won't be called. if (moduleUpdates.Status == ModuleUpdateStatus.Ready) @@ -352,7 +325,7 @@ public async ValueTask GetUpdatesAsync(CancellationToke _pendingUpdatedDesignTimeSolution = designTimeSolution; } - _diagnosticRefresher.RequestWorkspaceRefresh(); + UpdateApplyChangesDiagnostics(diagnosticData); var diagnostics = await EmitSolutionUpdateResults.GetHotReloadDiagnosticsAsync(solution, diagnosticData, rudeEdits, syntaxError, moduleUpdates.Status, cancellationToken).ConfigureAwait(false); return new ManagedHotReloadUpdates(moduleUpdates.Updates.FromContract(), diagnostics.FromContract()); diff --git a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageServiceBridge.cs b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageServiceBridge.cs new file mode 100644 index 0000000000000..f4071f8137054 --- /dev/null +++ b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageServiceBridge.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.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Debugger.Contracts.HotReload; + +namespace Microsoft.CodeAnalysis.EditAndContinue; + +/// +/// Exposes as a brokered service. +/// TODO (https://github.com/dotnet/roslyn/issues/72713): +/// Once debugger is updated to use the brokered service, this class should be removed and should be exported directly. +/// +internal sealed partial class ManagedEditAndContinueLanguageServiceBridge(EditAndContinueLanguageService service) : IManagedHotReloadLanguageService +{ + public ValueTask StartSessionAsync(CancellationToken cancellationToken) + => service.StartSessionAsync(cancellationToken); + + public ValueTask EndSessionAsync(CancellationToken cancellationToken) + => service.EndSessionAsync(cancellationToken); + + public ValueTask EnterBreakStateAsync(CancellationToken cancellationToken) + => service.EnterBreakStateAsync(cancellationToken); + + public ValueTask ExitBreakStateAsync(CancellationToken cancellationToken) + => service.ExitBreakStateAsync(cancellationToken); + + public ValueTask OnCapabilitiesChangedAsync(CancellationToken cancellationToken) + => service.OnCapabilitiesChangedAsync(cancellationToken); + + public async ValueTask GetUpdatesAsync(CancellationToken cancellationToken) + => (await service.GetUpdatesAsync(cancellationToken).ConfigureAwait(false)); + + public ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) + => service.CommitUpdatesAsync(cancellationToken); + + public ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) + => service.DiscardUpdatesAsync(cancellationToken); + + public ValueTask HasChangesAsync(string? sourceFilePath, CancellationToken cancellationToken) + => service.HasChangesAsync(sourceFilePath, cancellationToken); +} + diff --git a/src/EditorFeatures/Core/EditAndContinue/IActiveStatementTrackingService.cs b/src/EditorFeatures/Core/EditAndContinue/IActiveStatementTrackingService.cs index 1566ddd4880c6..75dca605d4b4b 100644 --- a/src/EditorFeatures/Core/EditAndContinue/IActiveStatementTrackingService.cs +++ b/src/EditorFeatures/Core/EditAndContinue/IActiveStatementTrackingService.cs @@ -11,9 +11,9 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; -internal interface IActiveStatementTrackingService : IWorkspaceService +internal interface IActiveStatementTrackingService : IWorkspaceService, IActiveStatementSpanLocator { - void StartTracking(Solution solution, IActiveStatementSpanProvider spanProvider); + void StartTracking(Solution solution, IActiveStatementSpanFactory spanProvider); void EndTracking(); @@ -22,12 +22,6 @@ internal interface IActiveStatementTrackingService : IWorkspaceService /// event Action TrackingChanged; - /// - /// 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(Solution solution, DocumentId? documentId, string filePath, CancellationToken cancellationToken); - /// /// Updates tracking spans with the latest positions of all active statements in the specified document snapshot (#line target document) and returns them. /// diff --git a/src/EditorFeatures/Core/Editor/TextBufferAssociatedViewService.cs b/src/EditorFeatures/Core/Editor/TextBufferAssociatedViewService.cs index 5ef3941846cd9..80165a5c981f7 100644 --- a/src/EditorFeatures/Core/Editor/TextBufferAssociatedViewService.cs +++ b/src/EditorFeatures/Core/Editor/TextBufferAssociatedViewService.cs @@ -97,7 +97,7 @@ private static IList GetTextViews(ITextBuffer textBuffer) { if (!s_map.TryGetValue(textBuffer, out var set)) { - return SpecializedCollections.EmptyList(); + return []; } return set.ToList(); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/TextEditApplication.cs b/src/EditorFeatures/Core/Editor/TextEditApplication.cs similarity index 86% rename from src/VisualStudio/Core/Def/ProjectSystem/TextEditApplication.cs rename to src/EditorFeatures/Core/Editor/TextEditApplication.cs index 9d06211e86f5b..c0acaa4266745 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/TextEditApplication.cs +++ b/src/EditorFeatures/Core/Editor/TextEditApplication.cs @@ -8,7 +8,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; +namespace Microsoft.CodeAnalysis.Editor; internal static class TextEditApplication { @@ -17,7 +17,7 @@ internal static void UpdateText(SourceText newText, ITextBuffer buffer, EditOpti var oldSnapshot = buffer.CurrentSnapshot; var oldText = oldSnapshot.AsText(); var changes = newText.GetTextChanges(oldText); - UpdateText(changes.ToImmutableArray(), buffer, oldSnapshot, oldText, options); + UpdateText([.. changes], buffer, oldSnapshot, oldText, options); } public static void UpdateText(ImmutableArray textChanges, ITextBuffer buffer, EditOptions options) @@ -31,7 +31,7 @@ public static void UpdateText(ImmutableArray textChanges, ITextBuffe private static void UpdateText(ImmutableArray textChanges, ITextBuffer buffer, ITextSnapshot oldSnapshot, SourceText oldText, EditOptions options) { using var edit = buffer.CreateEdit(options, reiteratedVersionNumber: null, editTag: null); - if (CodeAnalysis.Workspace.TryGetWorkspace(oldText.Container, out var workspace)) + if (Workspace.TryGetWorkspace(oldText.Container, out var workspace)) { var undoService = workspace.Services.GetRequiredService(); undoService.BeginUndoTransaction(oldSnapshot); diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs index ead1708f6bec9..0395dfb45c7fc 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs @@ -93,7 +93,7 @@ private static ISettingsProviderFactory GetOptionsProviderFactory(Workspac TryAddProviderForLanguage(LanguageNames.VisualBasic, workspace, providers); } - return new CombinedOptionsProviderFactory(providers.ToImmutableArray()); + return new CombinedOptionsProviderFactory([.. providers]); static void TryAddProviderForLanguage(string language, Workspace workspace, List> providers) { diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs index fb310e0fc78f7..b1a04b9621a9f 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs @@ -88,7 +88,7 @@ public ImmutableArray GetCurrentDataSnapshot() { lock (s_gate) { - return _snapshot.ToImmutableArray(); + return [.. _snapshot]; } } diff --git a/src/EditorFeatures/Core/EditorFeaturesResources.resx b/src/EditorFeatures/Core/EditorFeaturesResources.resx index 6b30388240e00..9fee174d54195 100644 --- a/src/EditorFeatures/Core/EditorFeaturesResources.resx +++ b/src/EditorFeatures/Core/EditorFeaturesResources.resx @@ -547,6 +547,9 @@ Do you want to proceed? Cancel + + Run + Changes @@ -923,4 +926,28 @@ Do you want to proceed? Roslyn Test Code Markup + + Obsolete symbol + + + AI-generated content may be inaccurate + + + GitHub Copilot + + + GitHub Copilot thinking... + + + Show an AI generated summary of this code + + + Tell me more + + + On-the-fly documentation + + + An error occurred while generating documentation for this code. + \ No newline at end of file diff --git a/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs b/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs index 978fac362050f..c6e58829f10e1 100644 --- a/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs +++ b/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs @@ -4,6 +4,7 @@ #nullable disable +using Microsoft.CodeAnalysis.Host; using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; namespace Microsoft.CodeAnalysis.Editor; @@ -132,6 +133,12 @@ internal static class PredefinedCommandHandlerNames /// public const string Rename = "Rename Command Handler"; + /// + /// Command handler for detecting user save commands, and using that to issue a request to run source generators + /// (when in mode). + /// + public const string SourceGeneratorSave = "Source Generator Save Command Handler"; + /// /// Command handler name for Rename Tracking cancellation. /// diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs index 872596c7274ca..d123648303e91 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs @@ -39,7 +39,7 @@ private static ImmutableArray GetSpans(RoslynNavigationBarItem underly using var _ = ArrayBuilder.GetInstance(out var spans); AddSpans(underlyingItem, spans); spans.SortAndRemoveDuplicates(Comparer.Default); - return spans.ToImmutable(); + return spans.ToImmutableAndClear(); static void AddSpans(RoslynNavigationBarItem underlyingItem, ArrayBuilder spans) { diff --git a/src/EditorFeatures/Core/ExternalAccess/IntelliCode/IntentProcessor.cs b/src/EditorFeatures/Core/ExternalAccess/IntelliCode/IntentProcessor.cs index 295958e5aca64..cb0a42fd4e3e4 100644 --- a/src/EditorFeatures/Core/ExternalAccess/IntelliCode/IntentProcessor.cs +++ b/src/EditorFeatures/Core/ExternalAccess/IntelliCode/IntentProcessor.cs @@ -88,7 +88,7 @@ public async Task> ComputeIntentsAsync(IntentReques convertedResults.AddIfNotNull(convertedIntent); } - return convertedResults.ToImmutable(); + return convertedResults.ToImmutableAndClear(); } private static async Task ConvertToIntelliCodeResultAsync( diff --git a/src/EditorFeatures/Core/ExternalAccess/UnitTesting/Api/UnitTestingGlobalOptions.cs b/src/EditorFeatures/Core/ExternalAccess/UnitTesting/Api/UnitTestingGlobalOptions.cs index 88602125ec598..105761cd9b6bb 100644 --- a/src/EditorFeatures/Core/ExternalAccess/UnitTesting/Api/UnitTestingGlobalOptions.cs +++ b/src/EditorFeatures/Core/ExternalAccess/UnitTesting/Api/UnitTestingGlobalOptions.cs @@ -5,18 +5,15 @@ using System; using System.Composition; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Remote; namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api; [Export(typeof(UnitTestingGlobalOptions)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class UnitTestingGlobalOptions(IGlobalOptionService globalOptions) +internal sealed class UnitTestingGlobalOptions() { - private readonly IGlobalOptionService _globalOptions = globalOptions; - - public bool IsServiceHubProcessCoreClr - => _globalOptions.GetOption(RemoteHostOptionsStorage.OOPCoreClr); +#pragma warning disable CA1822 // Mark members as static + public bool IsServiceHubProcessCoreClr => true; +#pragma warning restore CA1822 // Mark members as static } diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptGlobalOptions.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptGlobalOptions.cs index e80b36e4cea71..fe33d18f6e4f4 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptGlobalOptions.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptGlobalOptions.cs @@ -28,6 +28,8 @@ public void SetBackgroundAnalysisScope(bool openFilesOnly) { _globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, InternalLanguageNames.TypeScript, openFilesOnly ? BackgroundAnalysisScope.OpenFiles : BackgroundAnalysisScope.FullSolution); + _globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, InternalLanguageNames.TypeScript, + openFilesOnly ? CompilerDiagnosticsScope.OpenFiles : CompilerDiagnosticsScope.FullSolution); _globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.RemoveDocumentDiagnosticsOnDocumentClose, InternalLanguageNames.TypeScript, openFilesOnly); diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticService.cs deleted file mode 100644 index 5b9864deafe73..0000000000000 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticService.cs +++ /dev/null @@ -1,71 +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 System.Composition; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; - -[Export(typeof(IVSTypeScriptDiagnosticService)), Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class VSTypeScriptDiagnosticService(IDiagnosticService service) : IVSTypeScriptDiagnosticService -{ - private readonly IDiagnosticService _service = service; - - public Task> GetPushDiagnosticsAsync(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) - { - // This type is only for push diagnostics, which is now no longer how any of our diagnostic systems work. So - // this just returns nothing. - return SpecializedTasks.EmptyImmutableArray(); - } - - [Obsolete] - public IDisposable RegisterDiagnosticsUpdatedEventHandler(Action action) - => new EventHandlerWrapper(_service, action); - - public IDisposable RegisterDiagnosticsUpdatedEventHandler(Action> action) - => new EventHandlerWrapper(_service, action); - - private sealed class EventHandlerWrapper : IDisposable - { - private readonly IDiagnosticService _service; - private readonly EventHandler> _handler; - - [Obsolete] - internal EventHandlerWrapper(IDiagnosticService service, Action action) - { - _service = service; - _handler = (sender, argsCollection) => - { - foreach (var args in argsCollection) - action(new VSTypeScriptDiagnosticsUpdatedArgsWrapper(args)); - }; - _service.DiagnosticsUpdated += _handler; - } - - internal EventHandlerWrapper(IDiagnosticService service, Action> action) - { - _service = service; - _handler = (sender, argsCollection) => - { - action(ImmutableArray.CreateRange(argsCollection, static args => new VSTypeScriptDiagnosticsUpdatedArgsWrapper(args))); - }; - _service.DiagnosticsUpdated += _handler; - } - - public void Dispose() - { - _service.DiagnosticsUpdated -= _handler; - } - } -} diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs index 78d641cebbfc2..cfc295a276a4d 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Composition; using Microsoft.VisualStudio.LanguageServer.Client; @@ -57,6 +58,18 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.ProjectContextProvider = true; serverCapabilities.SupportsDiagnosticRequests = true; + serverCapabilities.DiagnosticProvider = new() + { + SupportsMultipleContextsDiagnostics = true, + DiagnosticKinds = + [ + new(PullDiagnosticCategories.Task), + new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), + new(PullDiagnosticCategories.DocumentAnalyzerSyntax), + new(PullDiagnosticCategories.DocumentAnalyzerSemantic), + ] + }; + return serverCapabilities; } diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs index da2e663f06bfd..b5527e89596a0 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; @@ -17,8 +18,9 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(analyzerService, diagnosticsRefresher, globalOptions) + IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { } @@ -28,7 +30,8 @@ internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( internal class VSTypeScriptWorkspacePullDiagnosticHandler( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, analyzerService, diagnosticsRefresher, globalOptions) + IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { } diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs index 699a25166b41c..16c437592808a 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs @@ -91,7 +91,7 @@ protected override IEnumerable GetSpansToTag(ITextView? textView, return base.GetSpansToTag(textView, subjectBuffer); } - return SpecializedCollections.SingletonEnumerable(visibleSpanOpt.Value); + return [visibleSpanOpt.Value]; } protected override async Task ProduceTagsAsync( diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.OpenTextBufferManager.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.OpenTextBufferManager.cs index f75305bfba53d..18a99ec18cde6 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.OpenTextBufferManager.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.OpenTextBufferManager.cs @@ -454,7 +454,7 @@ internal void ApplyConflictResolutionEdits(IInlineRenameReplacementInfo conflict // Show merge conflicts comments as unresolvable conflicts, and do not // show any other rename-related spans that overlap a merge conflict comment. mergeResult.MergeConflictCommentSpans.TryGetValue(document.Id, out var mergeConflictComments); - mergeConflictComments ??= SpecializedCollections.EmptyEnumerable(); + mergeConflictComments ??= []; foreach (var conflict in mergeConflictComments) { @@ -545,7 +545,7 @@ private static async Task> GetTextChangesFromTextDiffere if (oldDocument == newDocument) { // no changes - return SpecializedCollections.EmptyEnumerable(); + return []; } if (newDocument.Id != oldDocument.Id) @@ -558,7 +558,7 @@ private static async Task> GetTextChangesFromTextDiffere if (oldText == newText) { - return SpecializedCollections.EmptyEnumerable(); + return []; } var textChanges = newText.GetTextChanges(oldText).ToList(); diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index bb8d8217667c3..4f30186d98c73 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -247,7 +247,7 @@ private void InitializeOpenBuffers(SnapshotSpan triggerSpan) } this.UndoManager.CreateInitialState(this.ReplacementText, _triggerView.Selection, new SnapshotSpan(triggerSpan.Snapshot, startingSpan)); - _openTextBuffers[triggerSpan.Snapshot.TextBuffer].SetReferenceSpans(SpecializedCollections.SingletonEnumerable(startingSpan.ToTextSpan())); + _openTextBuffers[triggerSpan.Snapshot.TextBuffer].SetReferenceSpans([startingSpan.ToTextSpan()]); UpdateReferenceLocationsTask(); @@ -319,7 +319,7 @@ private void UpdateReferenceLocationsTask() // https://github.com/dotnet/roslyn/issues/40890 await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - RaiseSessionSpansUpdated(inlineRenameLocations.Locations.ToImmutableArray()); + RaiseSessionSpansUpdated([.. inlineRenameLocations.Locations]); return inlineRenameLocations; }); @@ -422,7 +422,7 @@ private void SetReferenceLocations(ImmutableArray location if (!documents.Any(static (d, locationsByDocument) => locationsByDocument.Contains(d.Id), locationsByDocument)) { - _openTextBuffers[textBuffer].SetReferenceSpans(SpecializedCollections.EmptyEnumerable()); + _openTextBuffers[textBuffer].SetReferenceSpans([]); } else { @@ -632,7 +632,7 @@ private void LogRenameSession(RenameLogMessage.UserActionOutcome outcome, bool p outcome, conflictResolutionFinishedComputing, previewChanges, - SpecializedCollections.EmptyList())); + replacementKinds: [])); } } diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameUIOptionsStorage.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameUIOptionsStorage.cs index 14629a946f40b..4e49a010b802e 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameUIOptionsStorage.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameUIOptionsStorage.cs @@ -10,4 +10,6 @@ internal sealed class InlineRenameUIOptionsStorage { public static readonly Option2 UseInlineAdornment = new("dotnet_rename_use_inline_adornment", defaultValue: true); public static readonly Option2 CollapseUI = new("dotnet_collapse_inline_rename_ui", defaultValue: false); + public static readonly Option2 CollapseSuggestionsPanel = new("dotnet_collapse_suggestions_in_inline_rename_ui", defaultValue: false); + public static readonly Option2 GetSuggestionsAutomatically = new("dotnet_rename_get_suggestions_automatically", defaultValue: false); } diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs index 2f738faae61df..df4f024f6bedc 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs @@ -82,7 +82,7 @@ internal CompletionSource( _asyncListener = asyncListener; _editorOptionsService = editorOptionsService; _isDebuggerTextView = textView is IDebuggerTextView; - _roles = textView.Roles.ToImmutableHashSet(); + _roles = [.. textView.Roles]; } public AsyncCompletionData.CompletionStartData InitializeCompletion( diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/FilterSet.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/FilterSet.cs index ebc2953cab5e5..6477cfdd23667 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/FilterSet.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/FilterSet.cs @@ -191,7 +191,7 @@ public ImmutableArray GetFilterStatesInSet() } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } /// @@ -218,7 +218,7 @@ public static ImmutableArray CombineFilterStates(Immu } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); void AddFilterState(ImmutableArray filterStates) { diff --git a/src/EditorFeatures/Core/IntelliSense/QuickInfo/EditorFeaturesOnTheFlyDocsElement.cs b/src/EditorFeatures/Core/IntelliSense/QuickInfo/EditorFeaturesOnTheFlyDocsElement.cs new file mode 100644 index 0000000000000..0f201bd23ce7b --- /dev/null +++ b/src/EditorFeatures/Core/IntelliSense/QuickInfo/EditorFeaturesOnTheFlyDocsElement.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.QuickInfo; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; + +internal sealed class EditorFeaturesOnTheFlyDocsElement(Document document, OnTheFlyDocsElement onTheFlyDocsElement) +{ + public Document Document { get; } = document; + public OnTheFlyDocsElement OnTheFlyDocsElement { get; } = onTheFlyDocsElement; +} diff --git a/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs b/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs index 3515897e3310b..e79ddf07c15bb 100644 --- a/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs +++ b/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs @@ -4,17 +4,18 @@ 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.Copilot; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.QuickInfo; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Adornments; @@ -81,8 +82,8 @@ private static async Task BuildInteractiveContentAsync( // Stack the first paragraph of the documentation comments with the last line of the description // to avoid vertical padding between the two. - var lastElement = elements[elements.Count - 1]; - elements[elements.Count - 1] = new ContainerElement( + var lastElement = elements[^1]; + elements[^1] = new ContainerElement( ContainerElementStyle.Stacked, lastElement, element); @@ -135,6 +136,9 @@ private static async Task BuildInteractiveContentAsync( } } + if (context is not null && quickInfoItem.OnTheFlyDocsElement is not null) + elements.Add(new EditorFeaturesOnTheFlyDocsElement(context.Document, quickInfoItem.OnTheFlyDocsElement)); + return new ContainerElement( ContainerElementStyle.Stacked | ContainerElementStyle.VerticalPadding, elements); @@ -154,7 +158,6 @@ internal static async Task BuildItemAsync( { var context = new IntellisenseQuickInfoBuilderContext(document, classificationOptions, lineFormattingOptions, threadingContext, operationExecutor, asyncListener, streamingPresenter); var content = await BuildInteractiveContentAsync(quickInfoItem, context, cancellationToken).ConfigureAwait(false); - return new IntellisenseQuickInfoItem(trackingSpan, content); } diff --git a/src/EditorFeatures/Core/LanguageServer/AbstractInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AbstractInProcLanguageClient.cs index 1bbb94b3add03..0038d5fbfe21b 100644 --- a/src/EditorFeatures/Core/LanguageServer/AbstractInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AbstractInProcLanguageClient.cs @@ -19,6 +19,8 @@ using Microsoft.VisualStudio.LanguageServer.Client; using Microsoft.VisualStudio.Threading; using Nerdbank.Streams; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Roslyn.LanguageServer.Protocol; using StreamJsonRpc; @@ -213,6 +215,7 @@ internal async Task> CreateAsync> CreateAsync Create( JsonRpc jsonRpc, + JsonSerializer jsonSerializer, ICapabilitiesProvider capabilitiesProvider, WellKnownLspServerKinds serverKind, AbstractLspLogger logger, @@ -232,6 +236,7 @@ public virtual AbstractLanguageServer Create( var server = new RoslynLanguageServer( LspServiceProvider, jsonRpc, + jsonSerializer, capabilitiesProvider, logger, hostServices, diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index fafe7b3331b95..b884102f56884 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Composition; @@ -40,9 +41,11 @@ internal class AlwaysActivateInProcLanguageClient( ILspServiceLoggerFactory lspLoggerFactory, IThreadingContext threadingContext, ExportProvider exportProvider, + IDiagnosticSourceManager diagnosticSourceManager, [ImportMany] IEnumerable> buildOnlyDiagnostics) : AbstractInProcLanguageClient(lspServiceProvider, globalOptions, lspLoggerFactory, threadingContext, exportProvider) { private readonly ExperimentalCapabilitiesProvider _experimentalCapabilitiesProvider = defaultCapabilitiesProvider; + private readonly IDiagnosticSourceManager _diagnosticSourceManager = diagnosticSourceManager; private readonly IEnumerable> _buildOnlyDiagnostics = buildOnlyDiagnostics; protected override ImmutableArray SupportedLanguages => ProtocolConstants.RoslynLspLanguages; @@ -69,27 +72,15 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.SupportsDiagnosticRequests = true; serverCapabilities.DiagnosticProvider ??= new(); + + // VS does not distinguish between document and workspace diagnostics, so we need to merge them. + var diagnosticSourceNames = _diagnosticSourceManager.GetDocumentSourceProviderNames(clientCapabilities) + .Concat(_diagnosticSourceManager.GetWorkspaceSourceProviderNames(clientCapabilities)) + .Distinct(); serverCapabilities.DiagnosticProvider = serverCapabilities.DiagnosticProvider with { SupportsMultipleContextsDiagnostics = true, - DiagnosticKinds = - [ - // Support a specialized requests dedicated to task-list items. This way the client can ask just - // for these, independently of other diagnostics. They can also throttle themselves to not ask if - // the task list would not be visible. - new(PullDiagnosticCategories.Task), - // Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on. - new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), - // Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic - // requests, allowing the former to quickly reach the user without blocking on the latter. In a - // similar vein, compiler diagnostics are explicitly distinct from analyzer-diagnostics, allowing - // the former to appear as soon as possible as they are much more critical for the user and should - // not be delayed by a slow analyzer. - new(PullDiagnosticCategories.DocumentCompilerSyntax), - new(PullDiagnosticCategories.DocumentCompilerSemantic), - new(PullDiagnosticCategories.DocumentAnalyzerSyntax), - new(PullDiagnosticCategories.DocumentAnalyzerSemantic), - ], + DiagnosticKinds = diagnosticSourceNames.Select(n => new VSInternalDiagnosticKind(n)).ToArray(), BuildOnlyDiagnosticIds = _buildOnlyDiagnostics .SelectMany(lazy => lazy.Metadata.BuildOnlyDiagnostics) .Distinct() @@ -117,7 +108,7 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa Range = true, Legend = new SemanticTokensLegend { - TokenTypes = SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes.ToArray(), + TokenTypes = [.. SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes], TokenModifiers = SemanticTokensSchema.TokenModifiers } }; diff --git a/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs index abce7a9ad1c88..385423e295bc6 100644 --- a/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs @@ -215,13 +215,13 @@ private void GetProjectItems(out ImmutableArray projec return; } - projectItems = documents.Select(d => + projectItems = [.. documents.Select(d => new NavigationBarProjectItem( d.Project.Name, d.Project.GetGlyph(), workspace: d.Project.Solution.Workspace, documentId: d.Id, - language: d.Project.Language)).OrderBy(projectItem => projectItem.Text).ToImmutableArray(); + language: d.Project.Language)).OrderBy(projectItem => projectItem.Text)]; var document = _subjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext(); selectedProjectItem = document != null diff --git a/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs b/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs index 75a264060fcc5..fae0c03b08895 100644 --- a/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs +++ b/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs @@ -589,7 +589,7 @@ public Task> CreateRemovedAnalyzerCo oldBuffer.CurrentSnapshot, "...", description, - originalSpans.ToArray()); + [.. originalSpans]); var changedBuffer = _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( _contentTypeRegistryService, @@ -597,7 +597,7 @@ public Task> CreateRemovedAnalyzerCo newBuffer.CurrentSnapshot, "...", description, - changedSpans.ToArray()); + [.. changedSpans]); #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) return await CreateNewDifferenceViewerAsync(leftWorkspace, rightWorkspace, originalBuffer, changedBuffer, zoomLevel, cancellationToken); diff --git a/src/EditorFeatures/Core/ReferenceHighlighting/NavigateToHighlightReferenceCommandHandler.cs b/src/EditorFeatures/Core/ReferenceHighlighting/NavigateToHighlightReferenceCommandHandler.cs index 72c0a5ab9f2f6..ca9c4cfdab6b4 100644 --- a/src/EditorFeatures/Core/ReferenceHighlighting/NavigateToHighlightReferenceCommandHandler.cs +++ b/src/EditorFeatures/Core/ReferenceHighlighting/NavigateToHighlightReferenceCommandHandler.cs @@ -87,7 +87,7 @@ private static ImmutableArray GetTags( tags.AddRange(tag.Span.GetSpans(span.Snapshot.TextBuffer)); tags.Sort(static (ss1, ss2) => ss1.Start - ss2.Start); - return tags.ToImmutable(); + return tags.ToImmutableAndClear(); } private static SnapshotSpan GetDestinationTag( diff --git a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs index f3fafcae3ed66..eb1e30f29407a 100644 --- a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs @@ -57,6 +57,12 @@ internal sealed partial class ReferenceHighlightingViewTaggerProvider( protected override TaggerDelay EventChangeDelay => TaggerDelay.Medium; + /// + /// We support frozen partial semantics, so we can quickly get reference highlights without building SG docs. We + /// will still run a tagging pass after the frozen-pass where we run again on non-frozen docs. + /// + protected override bool SupportsFrozenPartialSemantics => true; + protected override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer) { // Note: we don't listen for OnTextChanged. Text changes to this buffer will get @@ -149,6 +155,10 @@ private static async Task ProduceTagsAsync( var service = document.GetLanguageService(); if (service != null) { + // Ensure that if we're producing tags for frozen/partial documents, that we pass along that info so + // that we preserve that same behavior in OOP if we end up computing the tags there. + options = options with { FrozenPartialSemantics = context.FrozenPartialSemantics }; + // We only want to search inside documents that correspond to the snapshots // we're looking at var documentsToSearch = ImmutableHashSet.CreateRange(context.SpansToTag.Select(vt => vt.Document).WhereNotNull()); diff --git a/src/EditorFeatures/Core/Remote/RemoteHostOptionsStorage.cs b/src/EditorFeatures/Core/Remote/RemoteHostOptionsStorage.cs index b791ccb4817f2..743e4d23e9a76 100644 --- a/src/EditorFeatures/Core/Remote/RemoteHostOptionsStorage.cs +++ b/src/EditorFeatures/Core/Remote/RemoteHostOptionsStorage.cs @@ -11,8 +11,5 @@ internal sealed class RemoteHostOptionsStorage // use 64bit OOP public static readonly Option2 OOP64Bit = new("dotnet_code_analysis_in_separate_process", defaultValue: true); - // use coreclr host for OOP - public static readonly Option2 OOPCoreClr = new("dotnet_enable_core_clr_in_code_analysis_process", defaultValue: true); - public static readonly Option2 OOPServerGCFeatureFlag = new("dotnet_enable_server_garbage_collection_in_code_analysis_process", defaultValue: false); } diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 12dd252c7bec2..805117ccedac8 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -28,18 +28,25 @@ internal sealed class SolutionChecksumUpdater /// private readonly IGlobalOperationNotificationService? _globalOperationService; + private readonly IDocumentTrackingService _documentTrackingService; + /// /// Queue to push out text changes in a batched fashion when we hear about them. Because these should be short /// operations (only syncing text changes) we don't cancel this when we enter the paused state. We simply don't /// start queuing more requests into this until we become unpaused. /// - private readonly AsyncBatchingWorkQueue<(Document? oldDocument, Document? newDocument)> _textChangeQueue; + private readonly AsyncBatchingWorkQueue<(Document oldDocument, Document newDocument)> _textChangeQueue; /// /// Queue for kicking off the work to synchronize the primary workspace's solution. /// private readonly AsyncBatchingWorkQueue _synchronizeWorkspaceQueue; + /// + /// Queue for kicking off the work to synchronize the active document to the remote process. + /// + private readonly AsyncBatchingWorkQueue _synchronizeActiveDocumentQueue; + private readonly object _gate = new(); private bool _isPaused; @@ -53,24 +60,29 @@ public SolutionChecksumUpdater( _globalOperationService = workspace.Services.SolutionServices.ExportProvider.GetExports().FirstOrDefault()?.Value; _workspace = workspace; + _documentTrackingService = workspace.Services.GetRequiredService(); - _textChangeQueue = new AsyncBatchingWorkQueue<(Document? oldDocument, Document? newDocument)>( + _textChangeQueue = new AsyncBatchingWorkQueue<(Document oldDocument, Document newDocument)>( DelayTimeSpan.NearImmediate, SynchronizeTextChangesAsync, listener, shutdownToken); - // Use an equality comparer here as we will commonly get lots of change notifications that will all be - // associated with the same cancellation token controlling that batch of work. No need to enqueue the same - // token a huge number of times when we only need the single value of it when doing the work. _synchronizeWorkspaceQueue = new AsyncBatchingWorkQueue( DelayTimeSpan.NearImmediate, SynchronizePrimaryWorkspaceAsync, listener, shutdownToken); + _synchronizeActiveDocumentQueue = new AsyncBatchingWorkQueue( + DelayTimeSpan.NearImmediate, + SynchronizeActiveDocumentAsync, + listener, + shutdownToken); + // start listening workspace change event _workspace.WorkspaceChanged += OnWorkspaceChanged; + _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; if (_globalOperationService != null) { @@ -87,6 +99,7 @@ public void Shutdown() // Try to stop any work that is in progress. PauseWork(); + _documentTrackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; _workspace.WorkspaceChanged -= OnWorkspaceChanged; if (_globalOperationService != null) @@ -109,6 +122,7 @@ private void PauseWork() lock (_gate) { _synchronizeWorkspaceQueue.CancelExistingWork(); + _synchronizeActiveDocumentQueue.CancelExistingWork(); _isPaused = true; } } @@ -118,6 +132,7 @@ private void ResumeWork() lock (_gate) { _isPaused = false; + _synchronizeActiveDocumentQueue.AddWork(); _synchronizeWorkspaceQueue.AddWork(); } } @@ -134,12 +149,18 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) if (e.Kind == WorkspaceChangeKind.DocumentChanged) { - _textChangeQueue.AddWork((e.OldSolution.GetDocument(e.DocumentId), e.NewSolution.GetDocument(e.DocumentId))); + var oldDocument = e.OldSolution.GetDocument(e.DocumentId); + var newDocument = e.NewSolution.GetDocument(e.DocumentId); + if (oldDocument != null && newDocument != null) + _textChangeQueue.AddWork((oldDocument, newDocument)); } _synchronizeWorkspaceQueue.AddWork(); } + private void OnActiveDocumentChanged(object? sender, DocumentId? e) + => _synchronizeActiveDocumentQueue.AddWork(); + private async ValueTask SynchronizePrimaryWorkspaceAsync(CancellationToken cancellationToken) { var solution = _workspace.CurrentSolution; @@ -149,23 +170,33 @@ private async ValueTask SynchronizePrimaryWorkspaceAsync(CancellationToken cance using (Logger.LogBlock(FunctionId.SolutionChecksumUpdater_SynchronizePrimaryWorkspace, cancellationToken)) { - var workspaceVersion = solution.WorkspaceVersion; await client.TryInvokeAsync( solution, - (service, solution, cancellationToken) => service.SynchronizePrimaryWorkspaceAsync(solution, workspaceVersion, cancellationToken), + (service, solution, cancellationToken) => service.SynchronizePrimaryWorkspaceAsync(solution, cancellationToken), cancellationToken).ConfigureAwait(false); } } + private async ValueTask SynchronizeActiveDocumentAsync(CancellationToken cancellationToken) + { + var activeDocument = _documentTrackingService.TryGetActiveDocument(); + + var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); + if (client == null) + return; + + var solution = _workspace.CurrentSolution; + await client.TryInvokeAsync( + (service, cancellationToken) => service.SynchronizeActiveDocumentAsync(activeDocument, cancellationToken), + cancellationToken).ConfigureAwait(false); + } + private async ValueTask SynchronizeTextChangesAsync( - ImmutableSegmentedList<(Document? oldDocument, Document? newDocument)> values, + ImmutableSegmentedList<(Document oldDocument, Document newDocument)> values, CancellationToken cancellationToken) { foreach (var (oldDocument, newDocument) in values) { - if (oldDocument is null || newDocument is null) - continue; - cancellationToken.ThrowIfCancellationRequested(); await SynchronizeTextChangesAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false); } @@ -198,15 +229,15 @@ async ValueTask SynchronizeTextChangesAsync(Document oldDocument, Document newDo } // get text changes - var textChanges = newText.GetTextChanges(oldText); - if (textChanges.Count == 0) + var textChanges = newText.GetTextChanges(oldText).AsImmutable(); + if (textChanges.Length == 0) { // no changes return; } // whole document case - if (textChanges.Count == 1 && textChanges[0].Span.Length == oldText.Length) + if (textChanges.Length == 1 && textChanges[0].Span.Length == oldText.Length) { // no benefit here. pulling from remote host is more efficient return; diff --git a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCodeAction.cs b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCodeAction.cs index 2d1aecd212cff..f43e0a530435c 100644 --- a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCodeAction.cs +++ b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCodeAction.cs @@ -81,8 +81,7 @@ protected override async Task> ComputePreviewOp var solutionSet = await _renameTrackingCommitter.RenameSymbolAsync(cancellationToken).ConfigureAwait(false); - return SpecializedCollections.SingletonEnumerable( - (CodeActionOperation)new ApplyChangesOperation(solutionSet.RenamedSolution)); + return [new ApplyChangesOperation(solutionSet.RenamedSolution)]; } private bool TryInitializeRenameTrackingCommitter(CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs index 3691c669f2017..1bf2cf11cc79a 100644 --- a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs +++ b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs @@ -240,11 +240,7 @@ public bool ClearVisibleTrackingSession() // to trigger the diagnostic system to reanalyze, so we trigger it // manually. - _diagnosticAnalyzerService?.Reanalyze( - document.Project.Solution.Workspace, - projectIds: null, - documentIds: SpecializedCollections.SingletonEnumerable(document.Id), - highPriority: true); + _diagnosticAnalyzerService?.RequestDiagnosticRefresh(); } // Disallow the existing TrackingSession from triggering IdentifierFound. diff --git a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs index a6afcd6ae2beb..1fca9ecf51886 100644 --- a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs +++ b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs @@ -78,10 +78,9 @@ internal static bool ResetRenameTrackingStateWorker(Workspace workspace, Documen if (textBuffer == null) { var ex = new InvalidOperationException(string.Format( - "document with name {0} is open but textBuffer is null. Textcontainer is of type {1}. SourceText is: {2}", + "document with name {0} is open but textBuffer is null. Textcontainer is of type {1}.", document.Name, - text.Container.GetType().FullName, - text.ToString())); + text.Container.GetType().FullName)); FatalError.ReportAndCatch(ex); return false; } diff --git a/src/EditorFeatures/Core/SemanticSearch/SemanticSearchEditorWorkspace.cs b/src/EditorFeatures/Core/SemanticSearch/SemanticSearchEditorWorkspace.cs new file mode 100644 index 0000000000000..d099a979b91b4 --- /dev/null +++ b/src/EditorFeatures/Core/SemanticSearch/SemanticSearchEditorWorkspace.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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal sealed class SemanticSearchEditorWorkspace( + HostServices services, + SemanticSearchProjectConfiguration config, + IThreadingContext threadingContext, + IAsynchronousOperationListenerProvider listenerProvider) + : SemanticSearchWorkspace(services, config) +{ + private readonly IAsynchronousOperationListener _asyncListener = listenerProvider.GetListener(FeatureAttribute.SemanticSearch); + + private ITextBuffer? _queryTextBuffer; + private DocumentId? _queryDocumentId; + + public async Task OpenQueryDocumentAsync(ITextBuffer buffer, CancellationToken cancellationToken) + { + _queryTextBuffer = buffer; + + // initialize solution with default query, unless it has already been initialized: + var queryDocument = await UpdateQueryDocumentAsync(query: null, cancellationToken).ConfigureAwait(false); + + _queryDocumentId = queryDocument.Id; + + OnDocumentOpened(queryDocument.Id, buffer.AsTextContainer()); + } + + /// + /// Used by code actions through . + /// + protected override void ApplyDocumentTextChanged(DocumentId documentId, SourceText newText) + { + if (documentId == _queryDocumentId) + { + ApplyQueryDocumentTextChanged(newText); + } + } + + protected override void ApplyQueryDocumentTextChanged(SourceText newText) + { + Contract.ThrowIfNull(_queryTextBuffer); + + // update the buffer on UI thread: + + var completionToken = _asyncListener.BeginAsyncOperation(nameof(SemanticSearchEditorWorkspace) + "." + nameof(ApplyQueryDocumentTextChanged)); + _ = UpdateTextAsync().ReportNonFatalErrorAsync().CompletesAsyncOperation(completionToken); + + async Task UpdateTextAsync() + { + await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); + TextEditApplication.UpdateText(newText, _queryTextBuffer, EditOptions.DefaultMinimalChange); + } + } +} diff --git a/src/EditorFeatures/Core/SemanticSearch/SemanticSeatchTextBufferSupportsFeatureService.cs b/src/EditorFeatures/Core/SemanticSearch/SemanticSeatchTextBufferSupportsFeatureService.cs new file mode 100644 index 0000000000000..502a3f88462b5 --- /dev/null +++ b/src/EditorFeatures/Core/SemanticSearch/SemanticSeatchTextBufferSupportsFeatureService.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Editor.Shared; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +[ExportWorkspaceService(typeof(ITextBufferSupportsFeatureService), WorkspaceKind.SemanticSearch), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class SemanticSeatchTextBufferSupportsFeatureService() : ITextBufferSupportsFeatureService +{ + public bool SupportsCodeFixes(ITextBuffer textBuffer) + => true; + + public bool SupportsRefactorings(ITextBuffer textBuffer) + => true; + + public bool SupportsRename(ITextBuffer textBuffer) + => true; + + public bool SupportsNavigationToAnyPosition(ITextBuffer textBuffer) + => true; +} diff --git a/src/EditorFeatures/Core/Shared/Extensions/HostWorkspaceServicesExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/HostWorkspaceServicesExtensions.cs index 6b7e5a994289f..d2ef923f62d15 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/HostWorkspaceServicesExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/HostWorkspaceServicesExtensions.cs @@ -94,9 +94,7 @@ internal static IList SelectMatchingExtensionValues( where TMetadata : ILanguageMetadata { if (items == null) - { - return SpecializedCollections.EmptyList(); - } + return []; return items.Where(lazy => LanguageMatches(lazy.Metadata.Language, contentType, workspaceServices)). Select(lazy => lazy.Value).ToList(); diff --git a/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs index 1bad744ea46fc..5f77ff87d1177 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs @@ -44,7 +44,7 @@ public static void FormatAndApplyToBuffer( var formatter = document.GetRequiredLanguageService(); var options = textBuffer.GetSyntaxFormattingOptions(editorOptionsService, document.Project.Services, explicitFormat: false); - var result = formatter.GetFormattingResult(documentSyntax.Root, SpecializedCollections.SingletonEnumerable(span), options, rules, cancellationToken); + var result = formatter.GetFormattingResult(documentSyntax.Root, [span], options, rules, cancellationToken); var changes = result.GetTextChanges(cancellationToken); using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken)) diff --git a/src/EditorFeatures/Core/Shared/Tagging/Utilities/TagSpanIntervalTree.cs b/src/EditorFeatures/Core/Shared/Tagging/Utilities/TagSpanIntervalTree.cs index 8ec9a5537670b..fa6fae448e9e2 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/Utilities/TagSpanIntervalTree.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/Utilities/TagSpanIntervalTree.cs @@ -52,10 +52,9 @@ public bool HasSpanThatContains(SnapshotPoint point) } public IList> GetIntersectingSpans(SnapshotSpan snapshotSpan) - => SegmentedListPool.ComputeList( + => SegmentedListPool>.ComputeList( static (args, tags) => args.@this.AppendIntersectingSpansInSortedOrder(args.snapshotSpan, tags), - (@this: this, snapshotSpan), - _: (ITagSpan?)null); + (@this: this, snapshotSpan)); /// /// Gets all the spans that intersect with in sorted order and adds them to diff --git a/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs b/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs deleted file mode 100644 index fd97c32227d96..0000000000000 --- a/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities; - -/// -/// Base class that allows some helpers for detecting whether we're on the main WPF foreground thread, or -/// a background thread. It also allows scheduling work to the foreground thread at below input priority. -/// -internal class ForegroundThreadAffinitizedObject -{ - private readonly IThreadingContext _threadingContext; - - internal IThreadingContext ThreadingContext => _threadingContext; - - public ForegroundThreadAffinitizedObject(IThreadingContext threadingContext, bool assertIsForeground = false) - { - _threadingContext = threadingContext ?? throw new ArgumentNullException(nameof(threadingContext)); - - // ForegroundThreadAffinitizedObject might not necessarily be created on a foreground thread. - // AssertIsForeground here only if the object must be created on a foreground thread. - if (assertIsForeground) - { - // Assert we have some kind of foreground thread - Contract.ThrowIfFalse(threadingContext.HasMainThread); - - AssertIsForeground(); - } - } - - public bool IsForeground() - => _threadingContext.JoinableTaskContext.IsOnMainThread; - - public void AssertIsForeground() - { - var whenCreatedThread = _threadingContext.JoinableTaskContext.MainThread; - var currentThread = Thread.CurrentThread; - - // In debug, provide a lot more information so that we can track down unit test flakiness. - // This is too expensive to do in retail as it creates way too many allocations. - Debug.Assert(currentThread == whenCreatedThread, - "When created thread id : " + whenCreatedThread?.ManagedThreadId + "\r\n" + - "When created thread name: " + whenCreatedThread?.Name + "\r\n" + - "Current thread id : " + currentThread?.ManagedThreadId + "\r\n" + - "Current thread name : " + currentThread?.Name); - - // But, in retail, do the check as well, so that we can catch problems that happen in the wild. - Contract.ThrowIfFalse(_threadingContext.JoinableTaskContext.IsOnMainThread); - } - - public void AssertIsBackground() - => Contract.ThrowIfTrue(IsForeground()); - - /// - /// A helpful marker method that can be used by deriving classes to indicate that a - /// method can be called from any thread and is not foreground or background affinitized. - /// This is useful so that every method in deriving class can have some sort of marker - /// on each method stating the threading constraints (FG-only/BG-only/Any-thread). - /// - public static void ThisCanBeCalledOnAnyThread() - { - // Does nothing. - } - - public Task InvokeBelowInputPriorityAsync(Action action, CancellationToken cancellationToken = default) - { - if (IsForeground() && !IsInputPending()) - { - // Optimize to inline the action if we're already on the foreground thread - // and there's no pending user input. - action(); - - return Task.CompletedTask; - } - else - { - return Task.Factory.SafeStartNewFromAsync( - async () => - { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - action(); - }, - cancellationToken, - TaskScheduler.Default); - } - } - - /// - /// Returns true if any keyboard or mouse button input is pending on the message queue. - /// - protected static bool IsInputPending() - { - // The code below invokes into user32.dll, which is not available in non-Windows. - if (PlatformInformation.IsUnix) - { - return false; - } - - // The return value of GetQueueStatus is HIWORD:LOWORD. - // A non-zero value in HIWORD indicates some input message in the queue. - var result = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT); - - const uint InputMask = NativeMethods.QS_INPUT | (NativeMethods.QS_INPUT << 16); - return (result & InputMask) != 0; - } -} diff --git a/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs b/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs new file mode 100644 index 0000000000000..a68e544abd1b4 --- /dev/null +++ b/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities; + +internal interface IUIContextActivationService +{ + /// + /// Executes the specified action when the UIContext first becomes active, or immediately if it is already active + /// + void ExecuteWhenActivated(Guid uiContext, Action action); +} diff --git a/src/EditorFeatures/Core/Shared/Utilities/ThreadingContextTaskSchedulerProvider.cs b/src/EditorFeatures/Core/Shared/Utilities/ThreadingContextTaskSchedulerProvider.cs index fe6b2dcf2ae46..fe845adb6458c 100644 --- a/src/EditorFeatures/Core/Shared/Utilities/ThreadingContextTaskSchedulerProvider.cs +++ b/src/EditorFeatures/Core/Shared/Utilities/ThreadingContextTaskSchedulerProvider.cs @@ -29,7 +29,7 @@ private sealed class JoinableTaskFactoryTaskScheduler(JoinableTaskFactory joinab public override int MaximumConcurrencyLevel => 1; protected override IEnumerable GetScheduledTasks() - => SpecializedCollections.EmptyEnumerable(); + => []; protected override void QueueTask(Task task) { diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index 79e233429623f..8c93a15e83032 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -7,10 +7,8 @@ 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.Tagging; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; @@ -75,11 +73,18 @@ private sealed partial class TagSource private readonly AsyncBatchingWorkQueue _normalPriTagsChangedQueue; /// - /// Boolean specifies if this is the initial set of tags being computed or not. This queue is used to batch - /// up event change notifications and only dispatch one recomputation every - /// to actually produce the latest set of tags. + /// This queue is used to batch up event change notifications and only dispatch one recomputation every to actually produce the latest set of tags. /// - private readonly AsyncBatchingWorkQueue _eventChangeQueue; + private readonly AsyncBatchingWorkQueue _eventChangeQueue; + + /// + /// For taggers that support tagging frozen and non-frozen snapshots, this cancellation series controls the + /// non-frozen tagging pass. We want this to be separately cancellable so that if new events come in that we + /// cancel the expensive non-frozen tagging pass (which might be computing skeletons, SG docs, etc.), do the + /// next cheap frozen-tagging-pass, and then push the expensive-nonfrozen-tagging-pass to the end again. + /// + private readonly CancellationSeries _nonFrozenComputationCancellationSeries; #endregion @@ -156,18 +161,16 @@ public TagSource( _visibilityTracker = visibilityTracker; _dataSource = dataSource; _asyncListener = asyncListener; + _nonFrozenComputationCancellationSeries = new(_disposalTokenSource.Token); _workspaceRegistration = Workspace.GetWorkspaceRegistration(subjectBuffer.AsTextContainer()); - // Collapse all booleans added to just a max of two ('true' or 'false') representing if we're being - // asked for initial tags or not - // - // PERF: Use AsyncBatchingWorkQueue instead of AsyncBatchingWorkQueue because - // the latter has an async state machine that rethrows a very common cancellation exception. - _eventChangeQueue = new AsyncBatchingWorkQueue( + // PERF: Use AsyncBatchingWorkQueue<_, VoidResult> instead of AsyncBatchingWorkQueue<_> because the latter + // has an async state machine that rethrows a very common cancellation exception. + _eventChangeQueue = new AsyncBatchingWorkQueue( dataSource.EventChangeDelay.ComputeTimeDelay(), ProcessEventChangeAsync, - EqualityComparer.Default, + EqualityComparer.Default, asyncListener, _disposalTokenSource.Token); @@ -387,8 +390,8 @@ private void RaiseTagsChanged(ITextBuffer buffer, DiffResult difference) return; } - OnTagsChangedForBuffer(SpecializedCollections.SingletonCollection( - new KeyValuePair(buffer, difference)), + OnTagsChangedForBuffer( + [KeyValuePairUtil.Create(buffer, difference)], highPriority: false); } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 0c74f79bdae22..2d2c58b60cced 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; @@ -89,7 +90,7 @@ private void AccumulateTextChanges(TextContentChangedEventArgs contentChanged) var textChangeRange = new TextChangeRange(new TextSpan(c.OldSpan.Start, c.OldSpan.Length), c.NewLength); this.AccumulatedTextChanges = this.AccumulatedTextChanges == null ? textChangeRange - : this.AccumulatedTextChanges.Accumulate(SpecializedCollections.SingletonEnumerable(textChangeRange)); + : this.AccumulatedTextChanges.Accumulate([textChangeRange]); } break; @@ -168,13 +169,63 @@ private void OnEventSourceChanged(object? _1, TaggerEventArgs _2) => EnqueueWork(highPriority: false); private void EnqueueWork(bool highPriority) - => _eventChangeQueue.AddWork(highPriority, _dataSource.CancelOnNewWork); + { + // Cancel any expensive, in-flight, tagging work as there's now a request to perform lightweight tagging. + // Note: intentionally ignoring the return value here. We're enqueuing normal work here, so it has no + // associated token with it. + _ = _nonFrozenComputationCancellationSeries.CreateNext(); + EnqueueWork(highPriority, _dataSource.SupportsFrozenPartialSemantics, nonFrozenComputationToken: null); + } + + private void EnqueueWork(bool highPriority, bool frozenPartialSemantics, CancellationToken? nonFrozenComputationToken) + => _eventChangeQueue.AddWork( + new TagSourceQueueItem(highPriority, frozenPartialSemantics, nonFrozenComputationToken), + _dataSource.CancelOnNewWork); - private ValueTask ProcessEventChangeAsync(ImmutableSegmentedList changes, CancellationToken cancellationToken) + private async ValueTask ProcessEventChangeAsync( + ImmutableSegmentedList changes, CancellationToken cancellationToken) { + Contract.ThrowIfTrue(changes.IsEmpty); + // If any of the requests was high priority, then compute at that speed. - var highPriority = changes.Contains(true); - return new ValueTask(RecomputeTagsAsync(highPriority, cancellationToken)); + var highPriority = changes.Any(x => x.HighPriority); + + // If any of the requests are for frozen partial, then we do compute with frozen partial semantics. We + // always want these "fast but inaccurate" passes to happen first. That pass will then enqueue the work + // to do the slow-but-accurate pass. + var frozenPartialSemantics = changes.Any(t => t.FrozenPartialSemantics); + + if (!frozenPartialSemantics && _dataSource.SupportsFrozenPartialSemantics) + { + // We're asking for expensive tags, and this tagger supports frozen partial tags. Kick off the work + // to do this expensive tagging, but attach ourselves to the requested cancellation token so this + // expensive work can be canceled if new requests for frozen partial work come in. + + // Since we're not frozen-partial, all requests must have an associated cancellation token. And all but + // the last *must* be already canceled (since each is canceled as new work is added). + Contract.ThrowIfFalse(changes.All(t => !t.FrozenPartialSemantics)); + Contract.ThrowIfFalse(changes.All(t => t.NonFrozenComputationToken != null)); + Contract.ThrowIfFalse(changes.Take(changes.Count - 1).All(t => t.NonFrozenComputationToken!.Value.IsCancellationRequested)); + + var lastNonFrozenComputationToken = changes[^1].NonFrozenComputationToken!.Value; + + // Need a dedicated try/catch here since we're operating on a different token than the queue's token. + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(lastNonFrozenComputationToken, cancellationToken); + try + { + return await RecomputeTagsAsync(highPriority, frozenPartialSemantics, linkedTokenSource.Token).ConfigureAwait(false); + } + catch (OperationCanceledException ex) when (ExceptionUtilities.IsCurrentOperationBeingCancelled(ex, linkedTokenSource.Token)) + { + return default; + } + } + else + { + // Normal request to either compute frozen partial tags, or compute normal tags in a tagger that does + // *not* support frozen partial tagging. + return await RecomputeTagsAsync(highPriority, frozenPartialSemantics, cancellationToken).ConfigureAwait(false); + } } /// @@ -191,7 +242,10 @@ private ValueTask ProcessEventChangeAsync(ImmutableSegmentedList /// If this tagging request should be processed as quickly as possible with no extra delays added for it. /// - private async Task RecomputeTagsAsync(bool highPriority, CancellationToken cancellationToken) + private async Task RecomputeTagsAsync( + bool highPriority, + bool frozenPartialSemantics, + CancellationToken cancellationToken) { await _dataSource.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken).NoThrowAwaitable(); if (cancellationToken.IsCancellationRequested) @@ -236,9 +290,16 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( if (cancellationToken.IsCancellationRequested) return default; + if (frozenPartialSemantics) + { + spansToTag = spansToTag.SelectAsArray(ds => new DocumentSnapshotSpan( + ds.Document?.WithFrozenPartialSemantics(cancellationToken), + ds.SnapshotSpan)); + } + // Create a context to store pass the information along and collect the results. var context = new TaggerContext( - oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees); + oldState, frozenPartialSemantics, spansToTag, caretPosition, textChangeRange, oldTagTrees); await ProduceTagsAsync(context, cancellationToken).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) @@ -275,6 +336,12 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( // Once we've computed tags, pause ourselves if we're no longer visible. That way we don't consume any // machine resources that the user won't even notice. PauseIfNotVisible(); + + // If we were computing with frozen partial semantics here, enqueue work to compute *without* frozen + // partial snapshots so we move to accurate results shortly. Create and pass along a new cancellation + // token for this expensive work so that it can be canceled by future lightweight work. + if (frozenPartialSemantics) + this.EnqueueWork(highPriority, frozenPartialSemantics: false, _nonFrozenComputationCancellationSeries.CreateNext(default)); } return default; @@ -561,7 +628,7 @@ private DiffResult ComputeDifference( { // Compute this as a high priority work item to have the lease amount of blocking as possible. _dataSource.ThreadingContext.JoinableTaskFactory.Run(() => - this.RecomputeTagsAsync(highPriority: true, _disposalTokenSource.Token)); + this.RecomputeTagsAsync(highPriority: true, _dataSource.SupportsFrozenPartialSemantics, _disposalTokenSource.Token)); } _firstTagsRequest = false; diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs index 68a64bd1f494d..08b0c89e4d93a 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs @@ -95,6 +95,20 @@ internal abstract partial class AbstractAsynchronousTaggerProvider where T /// protected virtual bool CancelOnNewWork { get; } + /// + /// Whether or not this tagger would like to use frozen-partial snapshots to compute tags. If , tagging behaves normally, with a single call to after a batch of events comes in. If then tagging will happen in two passes. A first pass operating with frozen documents, + /// allowing the tagger to actually compute tags quickly, without waiting on skeleton references or source generated + /// documents to be up to date. Followed by a second, slower, pass on non-frozen documents that will then produce + /// the final accurate tags. Because this second pass is more expensive, it will be aggressively canceled and + /// pushed to the end when new normal work comes in. That way, when the user is doing things like typing, they'll + /// continuously be getting frozen-partial results quickly, but always with the final, full, correct results coming + /// at the end once enough idle time has passed. + /// + protected virtual bool SupportsFrozenPartialSemantics => false; + protected virtual void BeforeTagsChanged(ITextSnapshot snapshot) { } @@ -208,7 +222,7 @@ private void StoreTagSource(ITextView? textView, ITextBuffer subjectBuffer, TagS protected virtual IEnumerable GetSpansToTag(ITextView? textView, ITextBuffer subjectBuffer) { // For a standard tagger, the spans to tag is the span of the entire snapshot. - return SpecializedCollections.SingletonEnumerable(subjectBuffer.CurrentSnapshot.GetFullSpan()); + return [subjectBuffer.CurrentSnapshot.GetFullSpan()]; } /// diff --git a/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.SingleViewportTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.SingleViewportTaggerProvider.cs index b925dcce7ecda..a51d376bd7f02 100644 --- a/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.SingleViewportTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.SingleViewportTaggerProvider.cs @@ -47,6 +47,9 @@ protected override TaggerTextChangeBehavior TextChangeBehavior protected override SpanTrackingMode SpanTrackingMode => _callback.SpanTrackingMode; + protected override bool SupportsFrozenPartialSemantics + => _callback.SupportsFrozenPartialSemantics; + protected override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer) => _callback.CreateEventSource(textView, subjectBuffer); @@ -70,14 +73,14 @@ protected override IEnumerable GetSpansToTag(ITextView? textView, // above/below tagger should tag nothing. return _viewPortToTag == ViewPortToTag.InView ? base.GetSpansToTag(textView, subjectBuffer) - : SpecializedCollections.EmptyEnumerable(); + : []; } var visibleSpan = visibleSpanOpt.Value; // If we're the 'InView' tagger, tag what was visible. if (_viewPortToTag is ViewPortToTag.InView) - return SpecializedCollections.SingletonEnumerable(visibleSpan); + return [visibleSpan]; // For the above/below tagger, broaden the span to to the requested portion above/below what's visible, then // subtract out the visible range. diff --git a/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.cs index 30fef15c33246..0ff2f128f2294 100644 --- a/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.cs @@ -103,6 +103,9 @@ SingleViewportTaggerProvider CreateSingleViewportTaggerProvider(ViewPortToTag vi /// protected virtual SpanTrackingMode SpanTrackingMode => SpanTrackingMode.EdgeExclusive; + /// + protected virtual bool SupportsFrozenPartialSemantics { get; } + /// /// Indicates whether a tagger should be created for this text view and buffer. /// diff --git a/src/EditorFeatures/Core/Tagging/EfficientTagger.cs b/src/EditorFeatures/Core/Tagging/EfficientTagger.cs index 4eaa21b7d1fab..2b73738b90f79 100644 --- a/src/EditorFeatures/Core/Tagging/EfficientTagger.cs +++ b/src/EditorFeatures/Core/Tagging/EfficientTagger.cs @@ -30,10 +30,9 @@ internal abstract class EfficientTagger : ITagger, IDisposable where /// Default impl of the core interface. Forces an allocation. /// public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) - => SegmentedListPool.ComputeList( + => SegmentedListPool>.ComputeList( static (args, tags) => args.@this.AddTags(args.spans, tags), - (@this: this, spans), - _: (ITagSpan?)null); + (@this: this, spans)); public virtual event EventHandler? TagsChanged; diff --git a/src/EditorFeatures/Core/Tagging/TagSourceQueueItem.cs b/src/EditorFeatures/Core/Tagging/TagSourceQueueItem.cs new file mode 100644 index 0000000000000..84bb54b767d02 --- /dev/null +++ b/src/EditorFeatures/Core/Tagging/TagSourceQueueItem.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; + +namespace Microsoft.CodeAnalysis.Editor.Tagging; + +internal partial class AbstractAsynchronousTaggerProvider +{ + /// Specifies if this is the initial set of tags being computed or not, and no + /// artificial delays should be inserted when computing the tags. + /// Indicates if we should + /// compute with frozen partial semantics or not. + /// If is false, and this + /// queue does support computing frozen partial semantics (see ) + /// then this is a cancellation token that can cancel the expensive work being done if new frozen-partial work + /// is requested. + private record struct TagSourceQueueItem(bool HighPriority, bool FrozenPartialSemantics, CancellationToken? NonFrozenComputationToken); +} diff --git a/src/EditorFeatures/Core/Tagging/TaggerContext.cs b/src/EditorFeatures/Core/Tagging/TaggerContext.cs index de20723c6349e..fab148c0c801a 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerContext.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerContext.cs @@ -4,10 +4,7 @@ #nullable disable -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; @@ -15,7 +12,6 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Tagging; @@ -26,6 +22,14 @@ internal class TaggerContext where TTag : ITag internal ImmutableArray _spansTagged; public readonly SegmentedList> TagSpans = []; + /// + /// If the client should compute tags using frozen partial semantics. This generally should have no effect if tags + /// are computed within this process as the provided will be given the right frozen or + /// unfrozen documents. However, this is relevant when making calls to our external OOP server to ensure that it + /// also does the same when processing the request on its side. + /// + public bool FrozenPartialSemantics { get; } + public ImmutableArray SpansToTag { get; } public SnapshotPoint? CaretPosition { get; } @@ -48,22 +52,31 @@ internal class TaggerContext where TTag : ITag // For testing only. internal TaggerContext( - Document document, ITextSnapshot snapshot, + Document document, + ITextSnapshot snapshot, + bool frozenPartialSemantics, SnapshotPoint? caretPosition = null, TextChangeRange? textChangeRange = null) - : this(state: null, [new DocumentSnapshotSpan(document, snapshot.GetFullSpan())], - caretPosition, textChangeRange, existingTags: null) + : this( + state: null, + frozenPartialSemantics, + [new DocumentSnapshotSpan(document, snapshot.GetFullSpan())], + caretPosition, + textChangeRange, + existingTags: null) { } internal TaggerContext( object state, + bool frozenPartialSemantics, ImmutableArray spansToTag, SnapshotPoint? caretPosition, TextChangeRange? textChangeRange, ImmutableDictionary> existingTags) { this.State = state; + this.FrozenPartialSemantics = frozenPartialSemantics; this.SpansToTag = spansToTag; this.CaretPosition = caretPosition; this.TextChangeRange = textChangeRange; diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf index 296a70007ea64..fd8b9483a1338 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Relace přejmenování na řádku je pro identifikátor {0} aktivní. Pokud chcete získat přístup k dalším možnostem, znovu volejte přejmenování na řádku. Můžete kdykoli pokračovat v úpravách identifikátoru, který se přejmenovává. @@ -62,6 +72,16 @@ Získat nápovědu pro: {0} + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Přejít na základní typ @@ -202,6 +222,16 @@ Ne + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operátor – přetížení @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Upřesnit pomocí Copilotu @@ -292,6 +322,16 @@ Roslyn – revize testovacího kódu + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Dělený komentář @@ -312,6 +352,11 @@ Velikost tabulátoru + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Výsledky mohou být neúplné, protože řešení stále načítá projekty. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf index 826dbc3dc0ec4..2243c67170acd 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Für den Bezeichner "{0}" ist eine Inline-Umbenennungssitzung aktiv. Rufen Sie die Inline-Umbenennung erneut auf, um auf zusätzliche Optionen zuzugreifen. Sie können den Bezeichner, der gerade umbenannt wird, jederzeit weiterbearbeiten. @@ -62,6 +72,16 @@ Hilfe zu "{0}" abrufen + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Zu Basis wechseln @@ -202,6 +222,16 @@ Nein + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operator - überladen @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Mit Copilot verfeinern @@ -292,6 +322,16 @@ Roslyn-Testcodemarkup + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Kommentar teilen @@ -312,6 +352,11 @@ Tabstoppgröße + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Die Ergebnisse sind möglicherweise unvollständig, da die Projektmappe noch Projekte lädt. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf index dd538dd8a5c67..2cc1faf434f46 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Hay una sesión de cambio de nombre insertado que está activa para el identificador "{0}". Vuelva a invocar el cambio de nombre insertado para acceder a más opciones. Puede continuar editando el identificador cuyo nombre se va a cambiar en cualquier momento. @@ -62,6 +72,16 @@ Obtener ayuda para "{0}" + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Ir a base @@ -202,6 +222,16 @@ No + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operador: sobrecargado @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Refinar con Copilot @@ -292,6 +322,16 @@ Marcado de código de prueba de Roslyn + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Dividir comentario @@ -312,6 +352,11 @@ Tamaño de tabulación + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Los resultados pueden estar incompletos porque la solución aún está cargando proyectos. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf index e7a49dbd61378..f799bb0145e0f 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Une session de renommage inline est active pour l'identificateur '{0}'. Appelez à nouveau le renommage inline pour accéder à des options supplémentaires. Vous pouvez continuer à modifier l'identificateur renommé à tout moment. @@ -62,6 +72,16 @@ Obtenir de l'aide pour '{0}' + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Accéder à la base @@ -202,6 +222,16 @@ Non + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Opérateur - surchargé @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Affiner en utilisant Copilot @@ -292,6 +322,16 @@ Balisage du code de test Roslyn + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Diviser le commentaire @@ -312,6 +352,11 @@ Taille des tabulations + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Les résultats sont peut-être incomplets, car la solution est toujours en train de charger des projets. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf index f1fd95dd838d6..2a18a501c2814 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Per l'identificatore '{0}' è attiva una sessione di ridenominazione inline. Richiamare nuovamente la ridenominazione inline per accedere alle opzioni aggiuntive. È possibile continuare a modificare l'identificatore da rinominare in qualsiasi momento. @@ -62,6 +72,16 @@ Visualizza la Guida per '{0}' + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Vai a base @@ -202,6 +222,16 @@ No + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operatore - Overload @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Affinare con Copilot @@ -292,6 +322,16 @@ Markup del codice di test di Roslyn + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Dividi commento @@ -312,6 +352,11 @@ Dimensione tabulazione + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. I risultati potrebbero essere incompleti perché la soluzione ancora carica i progetti. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf index 972db8cfab01a..fcfee30eeda27 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. 識別子 '{0}' のインラインの名前変更セッションがアクティブです。追加オプションにアクセスするには、インラインの名前変更をもう一度呼び出します。名前を変更する識別子はいつでも引き続き編集できます。 @@ -62,6 +72,16 @@ '{0}' のヘルプの表示 + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base 基本へ移動 @@ -202,6 +222,16 @@ いいえ + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded 演算子 - オーバーロード @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Copilot を使用して絞り込む @@ -292,6 +322,16 @@ Roslyn テスト コード マークアップ + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment コメントの分割 @@ -312,6 +352,11 @@ タブのサイズ + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. ソリューションでプロジェクトを読み込み中であるため、結果は不完全になる可能性があります。 diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf index 205f935e256fa..6c8eaceb982f6 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. 식별자 '{0}'의 인라인 이름 바꾸기 세션이 활성 상태입니다. 추가 옵션에 액세스하려면 인라인 이름 바꾸기를 다시 호출하세요. 언제든지 이름을 바꾸려는 식별자를 계속 편집할 수 있습니다. @@ -62,6 +72,16 @@ '{0}'에 대한 도움 받기 + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base 기본으로 이동 @@ -202,6 +222,16 @@ 아니요 + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded 연산자 - 오버로드됨 @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Copilot을 사용하여 구체화 @@ -292,6 +322,16 @@ Roslyn 테스트 코드 마크업 + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment 주석 분할 @@ -312,6 +352,11 @@ 탭 크기 + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. 솔루션이 아직 프로젝트를 로드하는 중이므로 결과가 불완전할 수 있습니다. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf index 44844805ab126..13f60bbdf554b 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Wbudowana sesja zmieniania nazwy jest aktywna dla identyfikatora „{0}”. Wywołaj ponownie wbudowane zmienianie nazwy, aby uzyskać dostęp do dodatkowych opcji. W dowolnym momencie możesz kontynuować edytowanie identyfikatora, którego nazwa jest zmieniana. @@ -62,6 +72,16 @@ Uzyskaj pomoc dla „{0}” + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Przejdź do podstawy @@ -202,6 +222,16 @@ Nie + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operator — przeciążony @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Uściślanie przy użyciu funkcji Copilot @@ -292,6 +322,16 @@ Adiustacja kodu testowego Roslyn + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Podziel komentarz @@ -312,6 +352,11 @@ Rozmiar tabulacji + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Wyniki mogą być niekompletne, ponieważ rozwiązanie nadal ładuje projekty. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf index d4abbbcf97c5f..54d40bd631201 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Uma sessão de renomeação embutida está ativa para o identificador '{0}'. Invoque a renomeação embutida novamente para acessar opções adicionais. Você pode continuar a editar o identificador que está sendo renomeado a qualquer momento. @@ -62,6 +72,16 @@ Obter ajuda para '{0}' + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Ir Para a Base @@ -202,6 +222,16 @@ Não + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operador – Sobrecarregado @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Refinar usando o Copilot @@ -292,6 +322,16 @@ Marcação de Código de Teste Roslyn + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Dividir o comentário @@ -312,6 +352,11 @@ Tamanho da Tabulação + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Os resultados podem estar incompletos porque a solução ainda está carregando projetos. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf index e16baca18ecbe..0a8b441320d98 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Встроенный сеанс переименования активен для идентификатора "{0}". Снова вызовите встроенное переименование, чтобы получить доступ к дополнительным параметрам. Вы можете в любое время продолжить изменение переименованного идентификатора. @@ -62,6 +72,16 @@ Получить справку для "{0}" + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Перейти к базовому @@ -202,6 +222,16 @@ Нет + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Оператор — перегружен @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Уточнить с помощью Copilot @@ -292,6 +322,16 @@ Разметка тестового кода Roslyn + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Разделительный комментарий @@ -312,6 +352,11 @@ Размер интервала табуляции + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Результаты могут быть неполными, поскольку решение еще загружает проекты. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf index da6365927fcd9..3cdad91baa090 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. '{0}' tanımlayıcısı için bir satır içi yeniden adlandırma oturumu etkin. Ek seçeneklere erişmek için satır içi yeniden adlandırmayı tekrar çağırın. İstediğiniz zaman yeniden adlandırılan tanımlayıcıyı düzenlemeye devam edebilirsiniz. @@ -62,6 +72,16 @@ '{0}' için yardım alın + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Tabana Git @@ -202,6 +222,16 @@ Hayır + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded İşleç - Aşırı Yüklenmiş @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + Copilot kullanarak geliştir @@ -292,6 +322,16 @@ Roslyn Test Kodu İşaretlemesi + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Açıklamayı böl @@ -312,6 +352,11 @@ Sekme Boyutu + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Çözüm projeleri hala yüklemediğinden sonuçlar tamamlanmamış olabilir. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf index 877b5031ed46b..e58d77cdc186e 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. 标识符“{0}”的内联重命名会话处于活动状态。再次调用内联重命名以访问其他选项。可随时继续编辑正在重命名的标识符。 @@ -62,6 +72,16 @@ 获取有关“{0}”的帮助 + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base 转到基础映像 @@ -202,6 +222,16 @@ + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded 运算符-重载 @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + 使用 Copilot 优化 @@ -292,6 +322,16 @@ Roslyn 测试代码标记 + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment 拆分注释 @@ -312,6 +352,11 @@ 制表符大小 + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. 由于解决方案仍在加载项目,结果可能不完整。 diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf index 94cf68abd76d5..5c6301e581345 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. 識別碼 '{0}' 有正在使用的內嵌重新命名工作階段。再次叫用內嵌重新命名可存取其他選項。您隨時可以繼續編輯正在重新命名的識別碼。 @@ -62,6 +72,16 @@ 取得 '{0}' 的說明 + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base 移至基底 @@ -202,6 +222,16 @@ + + Obsolete symbol + Obsolete symbol + + + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded 運算子 - 多載 @@ -259,7 +289,7 @@ Refine using Copilot - Refine using Copilot + 使用 Copilot 縮小搜尋範圍 @@ -292,6 +322,16 @@ Roslyn 測試程式碼標記 + + Run + Run + + + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment 分割註解 @@ -312,6 +352,11 @@ 索引標籤大小 + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. 結果可能不完整,因為方案仍在載入專案。 diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/AbstractChangeSignatureTests.cs b/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/AbstractChangeSignatureTests.cs index 5cfcd4c680088..df222010dd24b 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/AbstractChangeSignatureTests.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/AbstractChangeSignatureTests.cs @@ -252,7 +252,7 @@ private static IEnumerable> GetPermutations(IEnumerable li { if (!list.Any()) { - yield return SpecializedCollections.EmptyEnumerable(); + yield return []; yield break; } @@ -286,9 +286,7 @@ private static IEnumerable GetListWithoutElementAtIndex(IEnumerable li private static IEnumerable> GetSubsets(IEnumerable list) { if (!list.Any()) - { - return SpecializedCollections.SingletonEnumerable(SpecializedCollections.EmptyEnumerable()); - } + return [[]]; var firstElement = list.Take(1); diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs index c4c24f20e54ad..3bffa0849dc4e 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs @@ -22,13 +22,10 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; @@ -45,7 +42,6 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions using OptionsCollectionAlias = CODESTYLE_UTILITIES::Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.OptionsCollection; #else using OptionsCollectionAlias = OptionsCollection; - using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions; #endif [UseExportProvider] @@ -155,9 +151,7 @@ private protected virtual IDocumentServiceProvider GetDocumentServiceProvider() => null; protected virtual TestComposition GetComposition() - => EditorTestCompositions.EditorFeatures - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)); + => EditorTestCompositions.EditorFeatures; protected virtual void InitializeWorkspace(EditorTestWorkspace workspace, TestParameters parameters) { @@ -301,8 +295,6 @@ protected async Task TestMissingAsync( var ps = parameters ?? TestParameters.Default; using var workspace = CreateWorkspaceFromOptions(initialMarkup, ps); - workspace.GlobalOptions.SetGlobalOption(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag, false); - var (actions, _) = await GetCodeActionsAsync(workspace, ps); var offeredActions = Environment.NewLine + string.Join(Environment.NewLine, actions.Select(action => action.Title)); @@ -495,8 +487,6 @@ private async Task TestAsync( using (var workspace = CreateWorkspaceFromOptions(initialMarkup, parameters)) { - workspace.GlobalOptions.SetGlobalOption(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag, false); - // Ideally this check would always run, but there are several hundred tests that would need to be // updated with {|Unnecessary:|} spans. if (unnecessarySpans.Any()) @@ -709,8 +699,6 @@ private static async Task VerifyAgainstWorkspaceDefinitionAsync(string expectedT { using (var expectedWorkspace = TestWorkspace.Create(expectedText, composition: composition)) { - expectedWorkspace.GlobalOptions.SetGlobalOption(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag, false); - var expectedSolution = expectedWorkspace.CurrentSolution; Assert.Equal(expectedSolution.Projects.Count(), newSolution.Projects.Count()); foreach (var project in newSolution.Projects) diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs index a7bad8a40a565..6128a5ceb1d2e 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs @@ -216,7 +216,7 @@ private static FixAllState GetFixAllState( if (scope == FixAllScope.Custom) { // Bulk fixing diagnostics in selected scope. - var diagnosticsToFix = ImmutableDictionary.CreateRange(SpecializedCollections.SingletonEnumerable(KeyValuePairUtil.Create(document, diagnostics.ToImmutableArray()))); + var diagnosticsToFix = ImmutableDictionary.CreateRange([KeyValuePairUtil.Create(document, diagnostics.ToImmutableArray())]); return FixAllState.Create(fixAllProvider, diagnosticsToFix, fixer, equivalenceKey, optionsProvider); } diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs index 23aac48821079..aaf8f45515377 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_GenerateTypeDialog.cs @@ -27,9 +27,7 @@ public abstract partial class AbstractUserDiagnosticTest { // TODO: IInlineRenameService requires WPF (https://github.com/dotnet/roslyn/issues/46153) private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeaturesWpf - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) .AddParts( - typeof(MockDiagnosticUpdateSourceRegistrationService), typeof(TestGenerateTypeOptionsService), typeof(TestProjectManagementService)); diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/MoveType/AbstractMoveTypeTest.cs b/src/EditorFeatures/DiagnosticsTestUtilities/MoveType/AbstractMoveTypeTest.cs index dc323d6b78d01..2f8d8892420d4 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/MoveType/AbstractMoveTypeTest.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/MoveType/AbstractMoveTypeTest.cs @@ -33,9 +33,7 @@ public abstract class AbstractMoveTypeTest : AbstractCodeActionTest // TODO: Requires WPF due to IInlineRenameService dependency (https://github.com/dotnet/roslyn/issues/46153) protected override TestComposition GetComposition() - => EditorTestCompositions.EditorFeaturesWpf - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)); + => EditorTestCompositions.EditorFeaturesWpf; protected override CodeRefactoringProvider CreateCodeRefactoringProvider(EditorTestWorkspace workspace, TestParameters parameters) => new MoveTypeCodeRefactoringProvider(); diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 3e75fd87eb280..0594aa086c502 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -36,9 +36,7 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.CodeFixes [UseExportProvider] public class CodeFixServiceTests { - private static readonly TestComposition s_compositionWithMockDiagnosticUpdateSourceRegistrationService = EditorTestCompositions.EditorFeatures - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)); + private static readonly TestComposition s_compositionWithMockDiagnosticUpdateSourceRegistrationService = EditorTestCompositions.EditorFeatures; [Fact] public async Task TestGetFirstDiagnosticWithFixAsync() @@ -49,7 +47,6 @@ public async Task TestGetFirstDiagnosticWithFixAsync() "; using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService, openDocuments: true); - Assert.IsType(workspace.GetService()); var diagnosticService = Assert.IsType(workspace.GetService()); var analyzerReference = new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); @@ -57,7 +54,7 @@ public async Task TestGetFirstDiagnosticWithFixAsync() var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => workspace.Services.GetRequiredService())); var fixService = new CodeFixService( - diagnosticService, logger, fixers, SpecializedCollections.EmptyEnumerable>()); + diagnosticService, logger, fixers, configurationProviders: []); var reference = new MockAnalyzerReference(); var project = workspace.CurrentSolution.Projects.Single().AddAnalyzerReference(reference); @@ -352,8 +349,6 @@ private static (EditorTestWorkspace workspace, DiagnosticAnalyzerService analyze new CodeChangeProviderMetadata("Test", languages: LanguageNames.CSharp))); var workspace = EditorTestWorkspace.CreateCSharp(code, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService, openDocuments: true); - Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( - workspace.CurrentSolution.Options.WithChangedOption(new OptionKey(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag), false)))); if (additionalDocument != null) { @@ -365,14 +360,13 @@ private static (EditorTestWorkspace workspace, DiagnosticAnalyzerService analyze var analyzerReference = new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); - Assert.IsType(workspace.GetService()); var diagnosticService = Assert.IsType(workspace.GetService()); var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => new TestErrorLogger())); var errorLogger = logger.First().Value; var configurationFixProviders = includeConfigurationFixProviders ? workspace.ExportProvider.GetExports() - : SpecializedCollections.EmptyEnumerable>(); + : []; var fixService = new CodeFixService( diagnosticService, @@ -417,10 +411,7 @@ private static void GetDocumentAndExtensionManager( } private static IEnumerable> CreateFixers() - { - return SpecializedCollections.SingletonEnumerable( - new Lazy(() => new MockFixer(), new CodeChangeProviderMetadata("Test", languages: LanguageNames.CSharp))); - } + => [new Lazy(() => new MockFixer(), new CodeChangeProviderMetadata("Test", languages: LanguageNames.CSharp))]; internal class MockFixer : CodeFixProvider { @@ -774,16 +765,15 @@ private static async Task> GetNuGetAndVsixCode var vsixFixers = vsixFixer != null ? SpecializedCollections.SingletonEnumerable(new Lazy(() => vsixFixer, new CodeChangeProviderMetadata(name: nameof(VsixCodeFixProvider), languages: LanguageNames.CSharp))) - : SpecializedCollections.EmptyEnumerable>(); + : []; using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService, openDocuments: true); - Assert.IsType(workspace.GetService()); var diagnosticService = Assert.IsType(workspace.GetService()); var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => workspace.Services.GetRequiredService())); var fixService = new CodeFixService( - diagnosticService, logger, vsixFixers, SpecializedCollections.EmptyEnumerable>()); + diagnosticService, logger, vsixFixers, configurationProviders: []); diagnosticAnalyzer ??= new MockAnalyzerReference.MockDiagnosticAnalyzer(); var analyzers = ImmutableArray.Create(diagnosticAnalyzer); @@ -1089,8 +1079,9 @@ void M() ? root.DescendantNodes().OfType().First().Span : root.DescendantNodes().OfType().First().Span; - await diagnosticIncrementalAnalyzer.GetDiagnosticsAsync( - sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, includeSuppressedDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); + await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( + sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, getDocuments: null, + includeSuppressedDiagnostics: true, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); var lowPriorityAnalyzers = new ConcurrentSet(); diff --git a/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs b/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs index 687c123b63ba3..8541cd06e00ff 100644 --- a/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs @@ -48,7 +48,7 @@ public void TestNoCyclesInFixProviders() var vbProviders = providersPerLanguage[LanguageNames.VisualBasic]; ExtensionOrderer.TestAccessor.CheckForCycles(vbProviders); - actualOrder = ExtensionOrderer.Order(vbProviders).ToArray(); + actualOrder = [.. ExtensionOrderer.Order(vbProviders)]; Assert.True(actualOrder.Length > 0); Assert.True(actualOrder.IndexOf(p => p.Metadata.Name == PredefinedCodeFixProviderNames.AddImport) < actualOrder.IndexOf(p => p.Metadata.Name == PredefinedCodeFixProviderNames.FullyQualify)); @@ -106,7 +106,7 @@ public void TestNoCyclesInRefactoringProviders() var vbProviders = providersPerLanguage[LanguageNames.VisualBasic]; ExtensionOrderer.TestAccessor.CheckForCycles(vbProviders); - actualOrder = ExtensionOrderer.Order(vbProviders).ToArray(); + actualOrder = [.. ExtensionOrderer.Order(vbProviders)]; Assert.True(actualOrder.Length > 0); } diff --git a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs index ac3a337bea539..79009add9ccad 100644 --- a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs +++ b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; @@ -15,7 +14,6 @@ using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -24,6 +22,8 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.CodeGeneration { + using static CSharpSyntaxTokens; + [Trait(Traits.Feature, Traits.Features.CodeGeneration)] public partial class CodeGenerationTests { @@ -1322,7 +1322,7 @@ public async Task AddAttributeToCompilationUnit() class C { } class D { }"; - await TestAddAttributeAsync(input, expected, typeof(SerializableAttribute), SyntaxFactory.Token(SyntaxKind.AssemblyKeyword)); + await TestAddAttributeAsync(input, expected, typeof(SerializableAttribute), AssemblyKeyword); } [Fact, Trait(Traits.Feature, Traits.Features.CodeGeneration)] @@ -1331,7 +1331,7 @@ public async Task AddAttributeWithWrongTarget() var input = "[|class C { } class D {} |]"; var expected = ""; await Assert.ThrowsAsync(async () => - await TestAddAttributeAsync(input, expected, typeof(SerializableAttribute), SyntaxFactory.Token(SyntaxKind.RefKeyword))); + await TestAddAttributeAsync(input, expected, typeof(SerializableAttribute), RefKeyword)); } [Fact, Trait(Traits.Feature, Traits.Features.CodeGeneration)] @@ -1402,7 +1402,7 @@ public async Task TestUpdateModifiers() // Comment 2 }"; var eol = SyntaxFactory.EndOfLine(@""); - var newModifiers = new[] { SyntaxFactory.Token(SyntaxKind.InternalKeyword).WithLeadingTrivia(eol) }.Concat( + var newModifiers = new[] { InternalKeyword.WithLeadingTrivia(eol) }.Concat( CreateModifierTokens(new Editing.DeclarationModifiers(isSealed: true, isPartial: true), LanguageNames.CSharp)); await TestUpdateDeclarationAsync(input, expected, modifiers: newModifiers); diff --git a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.VisualBasic.cs b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.VisualBasic.cs index 05b8f355cbd04..3c2a71bc2926f 100644 --- a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.VisualBasic.cs +++ b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.VisualBasic.cs @@ -408,7 +408,6 @@ End Event End Class"; static ImmutableArray GetExplicitInterfaceEvent(SemanticModel semanticModel) { - var parameterSymbols = SpecializedCollections.EmptyList(); return ImmutableArray.Create( new CodeGenerationEventSymbol( GetTypeSymbol(typeof(System.ComponentModel.INotifyPropertyChanged))(semanticModel), diff --git a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs index 5febe18aacdd4..30d7508fba1c1 100644 --- a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs +++ b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs @@ -941,9 +941,9 @@ public ImmutableArray ParseStatements(string statements) return default; } - using var listDisposer = ArrayBuilder.GetInstance(out var list); var delimiter = IsVisualBasic ? "\r\n" : ";"; var parts = statements.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); + var list = new FixedSizeArrayBuilder(parts.Length); foreach (var p in parts) { if (IsVisualBasic) @@ -956,7 +956,7 @@ public ImmutableArray ParseStatements(string statements) } } - return list.ToImmutable(); + return list.MoveToImmutable(); } public void Dispose() diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index ec4590845f359..2c7e97f9e7084 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -12,20 +12,16 @@ using Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics.CSharp; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; using Microsoft.CodeAnalysis.Editor.Test; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote.Diagnostics; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.UnitTests; using Roslyn.Test.Utilities; using Roslyn.Test.Utilities.TestGenerators; using Roslyn.Utilities; @@ -38,23 +34,12 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics public class DiagnosticAnalyzerServiceTests { private static readonly TestComposition s_featuresCompositionWithMockDiagnosticUpdateSourceRegistrationService = EditorTestCompositions.EditorFeatures - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)) .AddParts(typeof(TestDocumentTrackingService)); - private static readonly TestComposition s_editorFeaturesCompositionWithMockDiagnosticUpdateSourceRegistrationService = EditorTestCompositions.EditorFeatures - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)); + private static readonly TestComposition s_editorFeaturesCompositionWithMockDiagnosticUpdateSourceRegistrationService = EditorTestCompositions.EditorFeatures; private static AdhocWorkspace CreateWorkspace(Type[] additionalParts = null) - { - var workspace = new AdhocWorkspace(s_featuresCompositionWithMockDiagnosticUpdateSourceRegistrationService.AddParts(additionalParts).GetHostServices()); - - Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( - workspace.CurrentSolution.Options.WithChangedOption(new OptionKey(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag), false)))); - - return workspace; - } + => new AdhocWorkspace(s_featuresCompositionWithMockDiagnosticUpdateSourceRegistrationService.AddParts(additionalParts).GetHostServices()); private static IGlobalOptionService GetGlobalOptions(Workspace workspace) => workspace.Services.SolutionServices.ExportProvider.GetExportedValue(); @@ -78,23 +63,14 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var document = GetDocumentFromIncompleteProject(workspace); var exportProvider = workspace.Services.SolutionServices.ExportProvider; - Assert.IsType(exportProvider.GetExportedValue()); var service = Assert.IsType(exportProvider.GetExportedValue()); var analyzer = service.CreateIncrementalAnalyzer(workspace); var globalOptions = exportProvider.GetExportedValue(); - // listen to events - // check empty since this could be called to clear up existing diagnostics - service.DiagnosticsUpdated += (s, a) => - { - Assert.All(a, e => Assert.Empty(e.Diagnostics)); - }; - - await analyzer.GetDiagnosticsAsync( - workspace.CurrentSolution, projectId: null, documentId: null, includeSuppressedDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); - - // wait for all events to raised - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync().ConfigureAwait(false); + var diagnostics = await analyzer.GetDiagnosticsForIdsAsync( + workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, getDocuments: null, + includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); + Assert.NotEmpty(diagnostics); } [Fact] @@ -202,7 +178,6 @@ public async Task TestDisabledByDefaultAnalyzerEnabledWithEditorConfig(bool enab Assert.True(applied); var exportProvider = workspace.Services.SolutionServices.ExportProvider; - Assert.IsType(exportProvider.GetExportedValue()); var service = Assert.IsType(exportProvider.GetExportedValue()); var analyzer = service.CreateIncrementalAnalyzer(workspace); @@ -210,36 +185,27 @@ public async Task TestDisabledByDefaultAnalyzerEnabledWithEditorConfig(bool enab var syntaxDiagnostic = false; var semanticDiagnostic = false; var compilationDiagnostic = false; - service.DiagnosticsUpdated += (s, aCollection) => - { - foreach (var a in aCollection) - { - var diagnostics = a.Diagnostics; - var diagnostic = Assert.Single(diagnostics); - Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); - - if (diagnostic.Id == DisabledByDefaultAnalyzer.s_syntaxRule.Id) - { - syntaxDiagnostic = true; - } - else if (diagnostic.Id == DisabledByDefaultAnalyzer.s_semanticRule.Id) - { - semanticDiagnostic = true; - } - else if (diagnostic.Id == DisabledByDefaultAnalyzer.s_compilationRule.Id) - { - compilationDiagnostic = true; - } - } - }; // open document workspace.OpenDocument(document.Id); - await analyzer.ForceAnalyzeProjectAsync(document.Project, CancellationToken.None); + var diagnostics = await analyzer.ForceAnalyzeProjectAsync(document.Project, CancellationToken.None); - // wait for all events to raised - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync().ConfigureAwait(false); + foreach (var diagnostic in diagnostics) + { + if (diagnostic.Id == DisabledByDefaultAnalyzer.s_syntaxRule.Id) + { + syntaxDiagnostic = true; + } + else if (diagnostic.Id == DisabledByDefaultAnalyzer.s_semanticRule.Id) + { + semanticDiagnostic = true; + } + else if (diagnostic.Id == DisabledByDefaultAnalyzer.s_compilationRule.Id) + { + compilationDiagnostic = true; + } + } Assert.Equal(enabledWithEditorconfig, syntaxDiagnostic); Assert.Equal(enabledWithEditorconfig, semanticDiagnostic); @@ -254,169 +220,22 @@ private static async Task TestAnalyzerAsync( { var exportProvider = workspace.Services.SolutionServices.ExportProvider; - Assert.IsType(exportProvider.GetExportedValue()); var service = Assert.IsType(exportProvider.GetExportedValue()); - var globalOptions = exportProvider.GetExportedValue(); var analyzer = service.CreateIncrementalAnalyzer(workspace); var syntax = false; var semantic = false; - // listen to events - service.DiagnosticsUpdated += (s, aCollection) => - { - foreach (var a in aCollection) - { - var diagnostics = a.Diagnostics; - (syntax, semantic) = resultSetter(syntax, semantic, diagnostics); - } - }; + var diagnostics = await analyzer.ForceAnalyzeProjectAsync(document.Project, CancellationToken.None); - await analyzer.ForceAnalyzeProjectAsync(document.Project, CancellationToken.None); - - // wait for all events to raised - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync().ConfigureAwait(false); + (syntax, semantic) = resultSetter(syntax, semantic, diagnostics); // two should have been called. Assert.Equal(expectedSyntax, syntax); Assert.Equal(expectedSemantic, semantic); } - [Fact] - public async Task TestOpenFileOnlyAnalyzerDiagnostics() - { - using var workspace = CreateWorkspace(); - - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var globalOptions = exportProvider.GetExportedValue(); - - var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(new OpenFileOnlyAnalyzer())); - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); - - var project = workspace.AddProject( - ProjectInfo.Create( - ProjectId.CreateNewId(), - VersionStamp.Create(), - "CSharpProject", - "CSharpProject", - LanguageNames.CSharp)); - - var document = workspace.AddDocument(project.Id, "Empty.cs", SourceText.From("")); - - Assert.IsType(exportProvider.GetExportedValue()); - var service = Assert.IsType(exportProvider.GetExportedValue()); - var analyzer = service.CreateIncrementalAnalyzer(workspace); - - // listen to events - service.DiagnosticsUpdated += (s, aCollection) => - { - foreach (var a in aCollection) - { - if (workspace.IsDocumentOpen(a.DocumentId)) - { - var diagnostics = a.Diagnostics; - // check the diagnostics are reported - Assert.Equal(document.Id, a.DocumentId); - Assert.Equal(1, diagnostics.Length); - Assert.Equal(OpenFileOnlyAnalyzer.s_syntaxRule.Id, diagnostics[0].Id); - } - - if (a.DocumentId == document.Id && !workspace.IsDocumentOpen(a.DocumentId)) - { - // check the diagnostics reported are cleared - var diagnostics = a.Diagnostics; - Assert.Equal(0, diagnostics.Length); - } - } - }; - - // open document - workspace.OpenDocument(document.Id); - - // close document - workspace.CloseDocument(document.Id); - - // wait for all events to raised - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync().ConfigureAwait(false); - } - - [Fact] - public async Task TestSynchronizeWithBuild() - { - using var workspace = CreateWorkspace([typeof(NoCompilationLanguageService)]); - - var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(new NoNameAnalyzer())); - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); - - var language = NoCompilationConstants.LanguageName; - - var project = workspace.AddProject( - ProjectInfo.Create( - ProjectId.CreateNewId(), - VersionStamp.Create(), - "NoNameProject", - "NoNameProject", - language)); - - var filePath = "NoNameDoc.other"; - var document = workspace.AddDocument( - DocumentInfo.Create( - DocumentId.CreateNewId(project.Id), - "Empty", - loader: TextLoader.From(TextAndVersion.Create(SourceText.From(""), VersionStamp.Create(), filePath)), - filePath: filePath)); - - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - Assert.IsType(exportProvider.GetExportedValue()); - var service = Assert.IsType(exportProvider.GetExportedValue()); - var analyzer = service.CreateIncrementalAnalyzer(workspace); - var globalOptions = exportProvider.GetExportedValue(); - - var syntax = false; - - // listen to events - service.DiagnosticsUpdated += (s, aCollection) => - { - foreach (var a in aCollection) - { - var diagnostics = a.Diagnostics; - switch (diagnostics.Length) - { - case 0: - continue; - case 1: - syntax |= diagnostics[0].Id == NoNameAnalyzer.s_syntaxRule.Id; - continue; - default: - AssertEx.Fail("shouldn't reach here"); - continue; - } - } - }; - - // cause analysis - var location = Location.Create(document.FilePath, textSpan: default, lineSpan: default); - var properties = ImmutableDictionary.Empty.Add(WellKnownDiagnosticPropertyNames.Origin, WellKnownDiagnosticTags.Build); - - await service.SynchronizeWithBuildAsync( - workspace, - ImmutableDictionary>.Empty.Add( - document.Project.Id, - ImmutableArray.Create(DiagnosticData.Create(document.Project.Solution, Diagnostic.Create(NoNameAnalyzer.s_syntaxRule, location, properties), document.Project))), - new TaskQueue(service.Listener, TaskScheduler.Default), - onBuildCompleted: true, - CancellationToken.None); - - // wait for all events to raised - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync().ConfigureAwait(false); - - // two should have been called. - Assert.True(syntax); - - // we should reach here without crashing - } - [Fact] public void TestHostAnalyzerOrdering() { @@ -443,7 +262,6 @@ public void TestHostAnalyzerOrdering() "Dummy", LanguageNames.CSharp)); - Assert.IsType(exportProvider.GetExportedValue()); var service = Assert.IsType(exportProvider.GetExportedValue()); var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); @@ -494,32 +312,11 @@ public async Task TestHostAnalyzerErrorNotLeaking() filePath: "test.cs")})); var exportProvider = workspace.Services.SolutionServices.ExportProvider; - Assert.IsType(exportProvider.GetExportedValue()); var service = Assert.IsType(exportProvider.GetExportedValue()); - var called = false; - service.DiagnosticsUpdated += (s, eCollection) => - { - foreach (var e in eCollection) - { - var diagnostics = e.Diagnostics; - if (diagnostics.Length == 0) - { - continue; - } - - var liveId = (LiveDiagnosticUpdateArgsId)e.Id; - Assert.False(liveId.Analyzer is ProjectDiagnosticAnalyzer); - - called = true; - } - }; - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); - await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync(); - - Assert.True(called); + var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); + Assert.NotEmpty(diagnostics); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42353")] @@ -601,41 +398,25 @@ private static AdhocWorkspace CreateWorkspaceWithProjectAndAnalyzer(DiagnosticAn private static async Task TestFullSolutionAnalysisForProjectAsync(AdhocWorkspace workspace, Project project, bool expectAnalyzerExecuted) { var exportProvider = workspace.Services.SolutionServices.ExportProvider; - Assert.IsType(exportProvider.GetExportedValue()); var service = Assert.IsType(exportProvider.GetExportedValue()); - var globalOptions = exportProvider.GetExportedValue(); - - var called = false; - service.DiagnosticsUpdated += (s, eCollection) => - { - foreach (var e in eCollection) - { - var diagnostics = e.Diagnostics; - if (diagnostics.Length == 0) - { - continue; - } - - var liveId = (LiveDiagnosticUpdateArgsId)e.Id; - Assert.True(liveId.Analyzer is NamedTypeAnalyzer); - - called = true; - } - }; var incrementalAnalyzer = service.CreateIncrementalAnalyzer(project.Solution.Workspace); - await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync(); + var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); - Assert.Equal(expectAnalyzerExecuted, called); + if (expectAnalyzerExecuted) + { + Assert.NotEmpty(diagnostics); + } + else + { + Assert.Empty(diagnostics); + } } [Theory, CombinatorialData] internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool testMultiple) { using var workspace = CreateWorkspace(); - Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( - workspace.CurrentSolution.Options.WithChangedOption(new OptionKey(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag), false)))); var globalOptions = GetGlobalOptions(workspace); @@ -663,27 +444,18 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool Assert.True(applied); var exportProvider = workspace.Services.SolutionServices.ExportProvider; - Assert.IsType(exportProvider.GetExportedValue()); var service = Assert.IsType(exportProvider.GetExportedValue()); - var diagnostics = new ConcurrentSet(); - service.DiagnosticsUpdated += (s, eCollection) => - { - foreach (var e in eCollection) - diagnostics.AddRange(e.Diagnostics); - }; - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); var firstAdditionalDocument = project.AdditionalDocuments.FirstOrDefault(); workspace.OpenAdditionalDocument(firstAdditionalDocument.Id); - await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync(); + var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); var expectedCount = testMultiple ? 4 : 1; - Assert.Equal(expectedCount, diagnostics.Count); + Assert.Equal(expectedCount, diagnostics.Length); for (var i = 0; i < analyzers.Length; i++) { @@ -697,7 +469,7 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool var diagnostic = Assert.Single(applicableDiagnostics); Assert.Equal(diagnosticSpan, diagnostic.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text)); - diagnostics.Remove(diagnostic); + diagnostics = diagnostics.Remove(diagnostic); } } @@ -729,8 +501,6 @@ internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeS var analyzerReference = new AnalyzerImageReference(analyzers.ToImmutableArray()); using var workspace = EditorTestWorkspace.CreateCSharp("class A {}", composition: s_editorFeaturesCompositionWithMockDiagnosticUpdateSourceRegistrationService.AddParts(typeof(TestDocumentTrackingService))); - Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( - workspace.CurrentSolution.Options.WithChangedOption(new OptionKey(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag), false)))); workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, analysisScope); @@ -739,25 +509,9 @@ internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeS var project = workspace.CurrentSolution.Projects.Single(); var document = project.Documents.Single(); - Assert.IsType(workspace.GetService()); var service = Assert.IsType(workspace.GetService()); var globalOptions = workspace.GetService(); - DiagnosticData diagnostic = null; - service.DiagnosticsUpdated += (s, eCollection) => - { - foreach (var e in eCollection) - { - var diagnostics = e.Diagnostics; - if (diagnostics.Length == 0) - { - continue; - } - - diagnostic = Assert.Single(diagnostics); - } - }; - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); switch (analysisScope) @@ -780,9 +534,9 @@ internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeS throw ExceptionUtilities.UnexpectedValue(analysisScope); } - await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync(); + var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); + var diagnostic = diagnostics.SingleOrDefault(); if (includeAnalyzer) { Assert.True(diagnostic != null); @@ -857,8 +611,6 @@ void M() typeof(TestDocumentTrackingService)); using var workspace = new EditorTestWorkspace(composition); - Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( - workspace.CurrentSolution.Options.WithChangedOption(new OptionKey(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag), false)))); workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, analysisScope); workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles, isSourceGenerated); @@ -876,21 +628,9 @@ void M() else Assert.IsType(document); - Assert.IsType(workspace.GetService()); var service = Assert.IsType(workspace.GetService()); - var diagnostics = ArrayBuilder.GetInstance(); var text = await document.GetTextAsync(); - service.DiagnosticsUpdated += (s, eCollection) => - { - foreach (var e in eCollection) - { - diagnostics.AddRange( - e.Diagnostics - .Where(d => d.Id == IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId) - .OrderBy(d => d.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text))); - } - }; var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); @@ -919,13 +659,17 @@ void M() break; } - await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync(); + var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); + + diagnostics = diagnostics + .Where(d => d.Id == IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId) + .OrderBy(d => d.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text)) + .ToImmutableArray(); var root = await document.GetSyntaxRootAsync(); text = await document.GetTextAsync(); - Assert.Equal(2, diagnostics.Count); + Assert.Equal(2, diagnostics.Length); if (testPragma) { var pragma1 = root.FindTrivia(diagnostics[0].DataLocation.UnmappedFileSpan.GetClampedTextSpan(text).Start).ToString(); @@ -1110,8 +854,6 @@ void M() internal async Task TestGeneratorProducedDiagnostics(bool fullSolutionAnalysis, bool analyzeProject, TestHost testHost) { using var workspace = EditorTestWorkspace.CreateCSharp("// This file will get a diagnostic", composition: s_featuresCompositionWithMockDiagnosticUpdateSourceRegistrationService.WithTestHostParts(testHost)); - Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( - workspace.CurrentSolution.Options.WithChangedOption(new OptionKey(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag), false)))); var globalOptions = workspace.GetService(); @@ -1132,27 +874,10 @@ internal async Task TestGeneratorProducedDiagnostics(bool fullSolutionAnalysis, var service = Assert.IsType(workspace.GetService()); - var gotDiagnostics = false; - service.DiagnosticsUpdated += (s, eCollection) => - { - foreach (var e in eCollection) - { - var diagnostics = e.Diagnostics; - if (diagnostics.Length == 0) - continue; - - var liveId = (LiveDiagnosticUpdateArgsId)e.Id; - if (liveId.Analyzer is GeneratorDiagnosticsPlaceholderAnalyzer) - gotDiagnostics = true; - } - }; - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); - await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); + var diagnostics = await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync(); - - Assert.True(gotDiagnostics); + Assert.NotEmpty(diagnostics); } private static Document GetDocumentFromIncompleteProject(AdhocWorkspace workspace) @@ -1170,17 +895,10 @@ private static Document GetDocumentFromIncompleteProject(AdhocWorkspace workspac private static (bool, bool) AnalyzerResultSetter(bool syntax, bool semantic, ImmutableArray diagnostics) { - switch (diagnostics.Length) + foreach (var diagnostic in diagnostics) { - case 0: - break; - case 1: - syntax |= diagnostics[0].Id == Analyzer.s_syntaxRule.Id; - semantic |= diagnostics[0].Id == Analyzer.s_semanticRule.Id; - break; - default: - AssertEx.Fail("shouldn't reach here"); - break; + syntax |= diagnostic.Id == Analyzer.s_syntaxRule.Id; + semantic |= diagnostic.Id == Analyzer.s_semanticRule.Id; } return (syntax, semantic); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticDataTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticDataTests.cs index d038755b0263b..e0de118a7a3ad 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticDataTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticDataTests.cs @@ -4,14 +4,11 @@ #nullable disable -using System; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Xml.Linq; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticServiceTests.cs deleted file mode 100644 index bd444dac906fd..0000000000000 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticServiceTests.cs +++ /dev/null @@ -1,127 +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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics -{ - [UseExportProvider] - [Trait(Traits.Feature, Traits.Features.Diagnostics)] - public class DiagnosticServiceTests - { - private static DiagnosticService GetDiagnosticService(TestWorkspace workspace) - { - var diagnosticService = Assert.IsType(workspace.ExportProvider.GetExportedValue()); - - return diagnosticService; - } - - [Fact] - public void TestCleared() - { - using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures); - var mutex = new ManualResetEvent(false); - var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty); - var document2 = document.Project.AddDocument("TestDocument2", string.Empty); - - var diagnosticService = GetDiagnosticService(workspace); - - var source1 = new TestDiagnosticUpdateSource(); - diagnosticService.Register(source1); - - var source2 = new TestDiagnosticUpdateSource(); - diagnosticService.Register(source2); - - diagnosticService.DiagnosticsUpdated += MarkSet; - - // add bunch of data to the service for both sources - RaiseDiagnosticEvent(mutex, source1, workspace, document.Project.Id, document.Id, Tuple.Create(workspace, document)); - RaiseDiagnosticEvent(mutex, source1, workspace, document.Project.Id, document.Id, Tuple.Create(workspace, document.Project, document)); - RaiseDiagnosticEvent(mutex, source1, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2)); - - RaiseDiagnosticEvent(mutex, source2, workspace, document.Project.Id, null, Tuple.Create(workspace, document.Project)); - RaiseDiagnosticEvent(mutex, source2, workspace, null, null, Tuple.Create(workspace)); - - diagnosticService.DiagnosticsUpdated -= MarkSet; - - // confirm clear for a source - mutex.Reset(); - var count = 0; - diagnosticService.DiagnosticsUpdated += MarkCalled; - - source1.RaiseDiagnosticsClearedEvent(); - - mutex.WaitOne(); - return; - - void MarkCalled(object sender, ImmutableArray args) - { - foreach (var _ in args) - { - // event is serialized. no concurrent call - if (++count == 3) - { - mutex.Set(); - } - } - } - - void MarkSet(object sender, ImmutableArray args) - { - foreach (var _ in args) - mutex.Set(); - } - } - - private static DiagnosticData RaiseDiagnosticEvent(ManualResetEvent set, TestDiagnosticUpdateSource source, TestWorkspace workspace, ProjectId? projectId, DocumentId? documentId, object id) - { - set.Reset(); - - var diagnostic = CreateDiagnosticData(projectId, documentId); - - source.RaiseDiagnosticsUpdatedEvent( - ImmutableArray.Create(DiagnosticsUpdatedArgs.DiagnosticsCreated(id, workspace, workspace.CurrentSolution, projectId, documentId, ImmutableArray.Create(diagnostic)))); - - set.WaitOne(); - - return diagnostic; - } - - private static DiagnosticData CreateDiagnosticData(ProjectId? projectId, DocumentId? documentId) - { - return new DiagnosticData( - id: "test1", - category: "Test", - message: "test1 message", - severity: DiagnosticSeverity.Info, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: false, - warningLevel: 1, - customTags: ImmutableArray.Empty, - properties: ImmutableDictionary.Empty, - projectId, - location: new DiagnosticDataLocation(new("originalFile1", new(10, 10), new(20, 20)), documentId)); - } - - private class TestDiagnosticUpdateSource : IDiagnosticUpdateSource - { - public event EventHandler>? DiagnosticsUpdated; - public event EventHandler? DiagnosticsCleared; - - public void RaiseDiagnosticsUpdatedEvent(ImmutableArray args) - => DiagnosticsUpdated?.Invoke(this, args); - - public void RaiseDiagnosticsClearedEvent() - => DiagnosticsCleared?.Invoke(this, EventArgs.Empty); - } - } -} diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs index a26df2ed7e046..6b82044ca95c0 100644 --- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs @@ -8,11 +8,8 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Reflection; using System.Text; -using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; @@ -175,352 +172,355 @@ private static void VerifyConfigureSeverityCore(string expected, string language [Fact] public void CSharp_VerifyIDEDiagnosticSeveritiesAreConfigurable() { - var expected = @" -# IDE0001 -dotnet_diagnostic.IDE0001.severity = %value% + var expected = """ + # IDE0001 + dotnet_diagnostic.IDE0001.severity = %value% -# IDE0002 -dotnet_diagnostic.IDE0002.severity = %value% + # IDE0002 + dotnet_diagnostic.IDE0002.severity = %value% -# IDE0003 -dotnet_diagnostic.IDE0003.severity = %value% + # IDE0003 + dotnet_diagnostic.IDE0003.severity = %value% -# IDE0004 -dotnet_diagnostic.IDE0004.severity = %value% + # IDE0004 + dotnet_diagnostic.IDE0004.severity = %value% -# IDE0005 -dotnet_diagnostic.IDE0005.severity = %value% + # IDE0005 + dotnet_diagnostic.IDE0005.severity = %value% -# IDE0007 -dotnet_diagnostic.IDE0007.severity = %value% + # IDE0007 + dotnet_diagnostic.IDE0007.severity = %value% -# IDE0008 -dotnet_diagnostic.IDE0008.severity = %value% + # IDE0008 + dotnet_diagnostic.IDE0008.severity = %value% -# IDE0009 -dotnet_diagnostic.IDE0009.severity = %value% + # IDE0009 + dotnet_diagnostic.IDE0009.severity = %value% -# IDE0010 -dotnet_diagnostic.IDE0010.severity = %value% + # IDE0010 + dotnet_diagnostic.IDE0010.severity = %value% -# IDE0011 -dotnet_diagnostic.IDE0011.severity = %value% + # IDE0011 + dotnet_diagnostic.IDE0011.severity = %value% -# IDE0016 -dotnet_diagnostic.IDE0016.severity = %value% + # IDE0016 + dotnet_diagnostic.IDE0016.severity = %value% -# IDE0017 -dotnet_diagnostic.IDE0017.severity = %value% + # IDE0017 + dotnet_diagnostic.IDE0017.severity = %value% -# IDE0018 -dotnet_diagnostic.IDE0018.severity = %value% + # IDE0018 + dotnet_diagnostic.IDE0018.severity = %value% -# IDE0019 -dotnet_diagnostic.IDE0019.severity = %value% + # IDE0019 + dotnet_diagnostic.IDE0019.severity = %value% -# IDE0020 -dotnet_diagnostic.IDE0020.severity = %value% + # IDE0020 + dotnet_diagnostic.IDE0020.severity = %value% -# IDE0021 -dotnet_diagnostic.IDE0021.severity = %value% + # IDE0021 + dotnet_diagnostic.IDE0021.severity = %value% -# IDE0022 -dotnet_diagnostic.IDE0022.severity = %value% + # IDE0022 + dotnet_diagnostic.IDE0022.severity = %value% -# IDE0023 -dotnet_diagnostic.IDE0023.severity = %value% + # IDE0023 + dotnet_diagnostic.IDE0023.severity = %value% -# IDE0024 -dotnet_diagnostic.IDE0024.severity = %value% + # IDE0024 + dotnet_diagnostic.IDE0024.severity = %value% -# IDE0025 -dotnet_diagnostic.IDE0025.severity = %value% + # IDE0025 + dotnet_diagnostic.IDE0025.severity = %value% -# IDE0026 -dotnet_diagnostic.IDE0026.severity = %value% + # IDE0026 + dotnet_diagnostic.IDE0026.severity = %value% -# IDE0027 -dotnet_diagnostic.IDE0027.severity = %value% + # IDE0027 + dotnet_diagnostic.IDE0027.severity = %value% -# IDE0028 -dotnet_diagnostic.IDE0028.severity = %value% + # IDE0028 + dotnet_diagnostic.IDE0028.severity = %value% -# IDE0029 -dotnet_diagnostic.IDE0029.severity = %value% + # IDE0029 + dotnet_diagnostic.IDE0029.severity = %value% -# IDE0030 -dotnet_diagnostic.IDE0030.severity = %value% + # IDE0030 + dotnet_diagnostic.IDE0030.severity = %value% -# IDE0031 -dotnet_diagnostic.IDE0031.severity = %value% + # IDE0031 + dotnet_diagnostic.IDE0031.severity = %value% -# IDE0032 -dotnet_diagnostic.IDE0032.severity = %value% + # IDE0032 + dotnet_diagnostic.IDE0032.severity = %value% -# IDE0033 -dotnet_diagnostic.IDE0033.severity = %value% + # IDE0033 + dotnet_diagnostic.IDE0033.severity = %value% -# IDE0034 -dotnet_diagnostic.IDE0034.severity = %value% + # IDE0034 + dotnet_diagnostic.IDE0034.severity = %value% -# IDE0035 -dotnet_diagnostic.IDE0035.severity = %value% + # IDE0035 + dotnet_diagnostic.IDE0035.severity = %value% -# IDE0036 -dotnet_diagnostic.IDE0036.severity = %value% + # IDE0036 + dotnet_diagnostic.IDE0036.severity = %value% -# IDE0037 -dotnet_diagnostic.IDE0037.severity = %value% + # IDE0037 + dotnet_diagnostic.IDE0037.severity = %value% -# IDE0038 -dotnet_diagnostic.IDE0038.severity = %value% + # IDE0038 + dotnet_diagnostic.IDE0038.severity = %value% -# IDE0039 -dotnet_diagnostic.IDE0039.severity = %value% + # IDE0039 + dotnet_diagnostic.IDE0039.severity = %value% -# IDE0040 -dotnet_diagnostic.IDE0040.severity = %value% + # IDE0040 + dotnet_diagnostic.IDE0040.severity = %value% -# IDE0041 -dotnet_diagnostic.IDE0041.severity = %value% + # IDE0041 + dotnet_diagnostic.IDE0041.severity = %value% -# IDE0042 -dotnet_diagnostic.IDE0042.severity = %value% + # IDE0042 + dotnet_diagnostic.IDE0042.severity = %value% -# IDE0043 -dotnet_diagnostic.IDE0043.severity = %value% + # IDE0043 + dotnet_diagnostic.IDE0043.severity = %value% -# IDE0044 -dotnet_diagnostic.IDE0044.severity = %value% + # IDE0044 + dotnet_diagnostic.IDE0044.severity = %value% -# IDE0045 -dotnet_diagnostic.IDE0045.severity = %value% + # IDE0045 + dotnet_diagnostic.IDE0045.severity = %value% -# IDE0046 -dotnet_diagnostic.IDE0046.severity = %value% + # IDE0046 + dotnet_diagnostic.IDE0046.severity = %value% -# IDE0047 -dotnet_diagnostic.IDE0047.severity = %value% + # IDE0047 + dotnet_diagnostic.IDE0047.severity = %value% -# IDE0048 -dotnet_diagnostic.IDE0048.severity = %value% + # IDE0048 + dotnet_diagnostic.IDE0048.severity = %value% -# IDE0049 -dotnet_diagnostic.IDE0049.severity = %value% + # IDE0049 + dotnet_diagnostic.IDE0049.severity = %value% -# IDE0051 -dotnet_diagnostic.IDE0051.severity = %value% + # IDE0051 + dotnet_diagnostic.IDE0051.severity = %value% -# IDE0052 -dotnet_diagnostic.IDE0052.severity = %value% + # IDE0052 + dotnet_diagnostic.IDE0052.severity = %value% -# IDE0053 -dotnet_diagnostic.IDE0053.severity = %value% + # IDE0053 + dotnet_diagnostic.IDE0053.severity = %value% -# IDE0054 -dotnet_diagnostic.IDE0054.severity = %value% + # IDE0054 + dotnet_diagnostic.IDE0054.severity = %value% -# IDE0055 -dotnet_diagnostic.IDE0055.severity = %value% + # IDE0055 + dotnet_diagnostic.IDE0055.severity = %value% -# IDE0056 -dotnet_diagnostic.IDE0056.severity = %value% + # IDE0056 + dotnet_diagnostic.IDE0056.severity = %value% -# IDE0057 -dotnet_diagnostic.IDE0057.severity = %value% + # IDE0057 + dotnet_diagnostic.IDE0057.severity = %value% -# IDE0058 -dotnet_diagnostic.IDE0058.severity = %value% + # IDE0058 + dotnet_diagnostic.IDE0058.severity = %value% -# IDE0059 -dotnet_diagnostic.IDE0059.severity = %value% + # IDE0059 + dotnet_diagnostic.IDE0059.severity = %value% -# IDE0060 -dotnet_diagnostic.IDE0060.severity = %value% + # IDE0060 + dotnet_diagnostic.IDE0060.severity = %value% -# IDE0061 -dotnet_diagnostic.IDE0061.severity = %value% + # IDE0061 + dotnet_diagnostic.IDE0061.severity = %value% -# IDE0062 -dotnet_diagnostic.IDE0062.severity = %value% + # IDE0062 + dotnet_diagnostic.IDE0062.severity = %value% -# IDE0063 -dotnet_diagnostic.IDE0063.severity = %value% + # IDE0063 + dotnet_diagnostic.IDE0063.severity = %value% -# IDE0064 -dotnet_diagnostic.IDE0064.severity = %value% + # IDE0064 + dotnet_diagnostic.IDE0064.severity = %value% -# IDE0065 -dotnet_diagnostic.IDE0065.severity = %value% + # IDE0065 + dotnet_diagnostic.IDE0065.severity = %value% -# IDE0066 -dotnet_diagnostic.IDE0066.severity = %value% + # IDE0066 + dotnet_diagnostic.IDE0066.severity = %value% -# IDE0070 -dotnet_diagnostic.IDE0070.severity = %value% + # IDE0070 + dotnet_diagnostic.IDE0070.severity = %value% -# IDE0071 -dotnet_diagnostic.IDE0071.severity = %value% + # IDE0071 + dotnet_diagnostic.IDE0071.severity = %value% -# IDE0072 -dotnet_diagnostic.IDE0072.severity = %value% + # IDE0072 + dotnet_diagnostic.IDE0072.severity = %value% -# IDE0073 -dotnet_diagnostic.IDE0073.severity = %value% + # IDE0073 + dotnet_diagnostic.IDE0073.severity = %value% -# IDE0074 -dotnet_diagnostic.IDE0074.severity = %value% + # IDE0074 + dotnet_diagnostic.IDE0074.severity = %value% -# IDE0075 -dotnet_diagnostic.IDE0075.severity = %value% + # IDE0075 + dotnet_diagnostic.IDE0075.severity = %value% -# IDE0076 -dotnet_diagnostic.IDE0076.severity = %value% + # IDE0076 + dotnet_diagnostic.IDE0076.severity = %value% -# IDE0077 -dotnet_diagnostic.IDE0077.severity = %value% + # IDE0077 + dotnet_diagnostic.IDE0077.severity = %value% -# IDE0078 -dotnet_diagnostic.IDE0078.severity = %value% + # IDE0078 + dotnet_diagnostic.IDE0078.severity = %value% -# IDE0079 -dotnet_diagnostic.IDE0079.severity = %value% + # IDE0079 + dotnet_diagnostic.IDE0079.severity = %value% -# IDE0080 -dotnet_diagnostic.IDE0080.severity = %value% + # IDE0080 + dotnet_diagnostic.IDE0080.severity = %value% -# IDE0082 -dotnet_diagnostic.IDE0082.severity = %value% + # IDE0082 + dotnet_diagnostic.IDE0082.severity = %value% -# IDE0083 -dotnet_diagnostic.IDE0083.severity = %value% + # IDE0083 + dotnet_diagnostic.IDE0083.severity = %value% -# IDE0090 -dotnet_diagnostic.IDE0090.severity = %value% + # IDE0090 + dotnet_diagnostic.IDE0090.severity = %value% -# IDE0100 -dotnet_diagnostic.IDE0100.severity = %value% + # IDE0100 + dotnet_diagnostic.IDE0100.severity = %value% -# IDE0110 -dotnet_diagnostic.IDE0110.severity = %value% + # IDE0110 + dotnet_diagnostic.IDE0110.severity = %value% -# IDE0120 -dotnet_diagnostic.IDE0120.severity = %value% + # IDE0120 + dotnet_diagnostic.IDE0120.severity = %value% -# IDE0130 -dotnet_diagnostic.IDE0130.severity = %value% + # IDE0130 + dotnet_diagnostic.IDE0130.severity = %value% -# IDE0150 -dotnet_diagnostic.IDE0150.severity = %value% + # IDE0150 + dotnet_diagnostic.IDE0150.severity = %value% -# IDE0160 -dotnet_diagnostic.IDE0160.severity = %value% + # IDE0160 + dotnet_diagnostic.IDE0160.severity = %value% -# IDE0161 -dotnet_diagnostic.IDE0161.severity = %value% + # IDE0161 + dotnet_diagnostic.IDE0161.severity = %value% -# IDE0170 -dotnet_diagnostic.IDE0170.severity = %value% + # IDE0170 + dotnet_diagnostic.IDE0170.severity = %value% -# IDE0180 -dotnet_diagnostic.IDE0180.severity = %value% + # IDE0180 + dotnet_diagnostic.IDE0180.severity = %value% -# IDE0200 -dotnet_diagnostic.IDE0200.severity = %value% + # IDE0200 + dotnet_diagnostic.IDE0200.severity = %value% -# IDE0210 -dotnet_diagnostic.IDE0210.severity = %value% + # IDE0210 + dotnet_diagnostic.IDE0210.severity = %value% -# IDE0211 -dotnet_diagnostic.IDE0211.severity = %value% + # IDE0211 + dotnet_diagnostic.IDE0211.severity = %value% -# IDE0220 -dotnet_diagnostic.IDE0220.severity = %value% + # IDE0220 + dotnet_diagnostic.IDE0220.severity = %value% -# IDE0230 -dotnet_diagnostic.IDE0230.severity = %value% + # IDE0230 + dotnet_diagnostic.IDE0230.severity = %value% -# IDE0240 -dotnet_diagnostic.IDE0240.severity = %value% + # IDE0240 + dotnet_diagnostic.IDE0240.severity = %value% -# IDE0241 -dotnet_diagnostic.IDE0241.severity = %value% + # IDE0241 + dotnet_diagnostic.IDE0241.severity = %value% -# IDE0250 -dotnet_diagnostic.IDE0250.severity = %value% + # IDE0250 + dotnet_diagnostic.IDE0250.severity = %value% -# IDE0251 -dotnet_diagnostic.IDE0251.severity = %value% + # IDE0251 + dotnet_diagnostic.IDE0251.severity = %value% -# IDE0260 -dotnet_diagnostic.IDE0260.severity = %value% + # IDE0260 + dotnet_diagnostic.IDE0260.severity = %value% -# IDE0270 -dotnet_diagnostic.IDE0270.severity = %value% + # IDE0270 + dotnet_diagnostic.IDE0270.severity = %value% -# IDE0280 -dotnet_diagnostic.IDE0280.severity = %value% + # IDE0280 + dotnet_diagnostic.IDE0280.severity = %value% -# IDE0290 -dotnet_diagnostic.IDE0290.severity = %value% + # IDE0290 + dotnet_diagnostic.IDE0290.severity = %value% -# IDE0300 -dotnet_diagnostic.IDE0300.severity = %value% + # IDE0300 + dotnet_diagnostic.IDE0300.severity = %value% -# IDE0301 -dotnet_diagnostic.IDE0301.severity = %value% + # IDE0301 + dotnet_diagnostic.IDE0301.severity = %value% -# IDE0302 -dotnet_diagnostic.IDE0302.severity = %value% + # IDE0302 + dotnet_diagnostic.IDE0302.severity = %value% -# IDE0303 -dotnet_diagnostic.IDE0303.severity = %value% + # IDE0303 + dotnet_diagnostic.IDE0303.severity = %value% -# IDE0304 -dotnet_diagnostic.IDE0304.severity = %value% + # IDE0304 + dotnet_diagnostic.IDE0304.severity = %value% -# IDE0305 -dotnet_diagnostic.IDE0305.severity = %value% + # IDE0305 + dotnet_diagnostic.IDE0305.severity = %value% -# IDE1005 -dotnet_diagnostic.IDE1005.severity = %value% + # IDE0320 + dotnet_diagnostic.IDE0320.severity = %value% -# IDE1006 -dotnet_diagnostic.IDE1006.severity = %value% + # IDE1005 + dotnet_diagnostic.IDE1005.severity = %value% -# IDE1007 -dotnet_diagnostic.IDE1007.severity = %value% + # IDE1006 + dotnet_diagnostic.IDE1006.severity = %value% -# IDE2000 -dotnet_diagnostic.IDE2000.severity = %value% + # IDE1007 + dotnet_diagnostic.IDE1007.severity = %value% -# IDE2001 -dotnet_diagnostic.IDE2001.severity = %value% + # IDE2000 + dotnet_diagnostic.IDE2000.severity = %value% -# IDE2002 -dotnet_diagnostic.IDE2002.severity = %value% + # IDE2001 + dotnet_diagnostic.IDE2001.severity = %value% -# IDE2003 -dotnet_diagnostic.IDE2003.severity = %value% + # IDE2002 + dotnet_diagnostic.IDE2002.severity = %value% -# IDE2004 -dotnet_diagnostic.IDE2004.severity = %value% + # IDE2003 + dotnet_diagnostic.IDE2003.severity = %value% -# IDE2005 -dotnet_diagnostic.IDE2005.severity = %value% + # IDE2004 + dotnet_diagnostic.IDE2004.severity = %value% -# IDE2006 -dotnet_diagnostic.IDE2006.severity = %value% + # IDE2005 + dotnet_diagnostic.IDE2005.severity = %value% -# RE0001 -dotnet_diagnostic.RE0001.severity = %value% + # IDE2006 + dotnet_diagnostic.IDE2006.severity = %value% -# JSON001 -dotnet_diagnostic.JSON001.severity = %value% + # RE0001 + dotnet_diagnostic.RE0001.severity = %value% -# JSON002 -dotnet_diagnostic.JSON002.severity = %value% -"; + # JSON001 + dotnet_diagnostic.JSON001.severity = %value% + + # JSON002 + dotnet_diagnostic.JSON002.severity = %value% + """; VerifyConfigureSeverityCore(expected, LanguageNames.CSharp); } @@ -895,6 +895,7 @@ public void CSharp_VerifyIDECodeStyleOptionsAreConfigurable() ("IDE0303", "dotnet_style_prefer_collection_expression", "when_types_loosely_match"), ("IDE0304", "dotnet_style_prefer_collection_expression", "when_types_loosely_match"), ("IDE0305", "dotnet_style_prefer_collection_expression", "when_types_loosely_match"), + ("IDE0320", "csharp_prefer_static_anonymous_function", "true"), ("IDE1005", "csharp_style_conditional_delegate_call", "true"), ("IDE1006", null, null), ("IDE1007", null, null), diff --git a/src/EditorFeatures/Test/Diagnostics/MockDiagnosticService.cs b/src/EditorFeatures/Test/Diagnostics/MockDiagnosticService.cs deleted file mode 100644 index 571b269b2cf55..0000000000000 --- a/src/EditorFeatures/Test/Diagnostics/MockDiagnosticService.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics -{ - [Export(typeof(IDiagnosticService)), Shared, PartNotDiscoverable] - internal class MockDiagnosticService : IDiagnosticService - { - public const string DiagnosticId = "MockId"; - - public event EventHandler> DiagnosticsUpdated { add { } remove { } } - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public MockDiagnosticService() - { - } - } -} diff --git a/src/EditorFeatures/Test/Diagnostics/SuppressMessageAttributeWorkspaceTests.cs b/src/EditorFeatures/Test/Diagnostics/SuppressMessageAttributeWorkspaceTests.cs index 651608fafd8d8..3d1d7b086fe04 100644 --- a/src/EditorFeatures/Test/Diagnostics/SuppressMessageAttributeWorkspaceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/SuppressMessageAttributeWorkspaceTests.cs @@ -24,9 +24,7 @@ namespace Microsoft.CodeAnalysis.UnitTests.Diagnostics [UseExportProvider] public class SuppressMessageAttributeWorkspaceTests : SuppressMessageAttributeTests { - private static readonly TestComposition s_compositionWithMockDiagnosticUpdateSourceRegistrationService = EditorTestCompositions.EditorFeatures - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)); + private static readonly TestComposition s_compositionWithMockDiagnosticUpdateSourceRegistrationService = EditorTestCompositions.EditorFeatures; private static readonly Lazy _unconditionalSuppressMessageRef = new(() => { diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs new file mode 100644 index 0000000000000..fd8a2b223c28c --- /dev/null +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs @@ -0,0 +1,184 @@ +// Licensed to the .NET Foundation under one or more 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.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.BrokeredServices; +using Microsoft.CodeAnalysis.BrokeredServices.UnitTests; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; +using Microsoft.CodeAnalysis.Editor.UnitTests; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.UnitTests; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; +using Xunit; +using DebuggerContracts = Microsoft.VisualStudio.Debugger.Contracts.HotReload; + +namespace Roslyn.VisualStudio.Next.UnitTests.EditAndContinue +{ + [UseExportProvider] + public class EditAndContinueLanguageServiceTests + { + private static string Inspect(DiagnosticData d) + => $"{d.Severity} {d.Id}:" + + (!string.IsNullOrWhiteSpace(d.DataLocation.UnmappedFileSpan.Path) ? $" {d.DataLocation.UnmappedFileSpan.Path}({d.DataLocation.UnmappedFileSpan.StartLinePosition.Line}, {d.DataLocation.UnmappedFileSpan.StartLinePosition.Character}, {d.DataLocation.UnmappedFileSpan.EndLinePosition.Line}, {d.DataLocation.UnmappedFileSpan.EndLinePosition.Character}):" : "") + + $" {d.Message}"; + + private static string Inspect(DebuggerContracts.ManagedHotReloadDiagnostic d) + => $"{d.Severity} {d.Id}:" + + (!string.IsNullOrWhiteSpace(d.FilePath) ? $" {d.FilePath}({d.Span.StartLine}, {d.Span.StartColumn}, {d.Span.EndLine}, {d.Span.EndColumn}):" : "") + + $" {d.Message}"; + + [Theory, CombinatorialData] + public async Task Test(bool commitChanges) + { + var localComposition = EditorTestCompositions.LanguageServerProtocolEditorFeatures + .AddExcludedPartTypes(typeof(EditAndContinueService)) + .AddParts( + typeof(NoCompilationLanguageService), + typeof(MockHostWorkspaceProvider), + typeof(MockServiceBrokerProvider), + typeof(MockEditAndContinueService), + typeof(MockManagedHotReloadService)); + + using var localWorkspace = new TestWorkspace(composition: localComposition); + + var globalOptions = localWorkspace.GetService(); + ((MockHostWorkspaceProvider)localWorkspace.GetService()).Workspace = localWorkspace; + + ((MockServiceBroker)localWorkspace.GetService().ServiceBroker).CreateService = t => t switch + { + _ when t == typeof(DebuggerContracts.IHotReloadLogger) => new MockHotReloadLogger(), + _ => throw ExceptionUtilities.UnexpectedValue(t) + }; + + MockEditAndContinueService mockEncService; + + mockEncService = (MockEditAndContinueService)localWorkspace.GetService(); + + var localService = localWorkspace.GetService(); + + var projectId = ProjectId.CreateNewId(); + var documentId = DocumentId.CreateNewId(projectId); + + await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution + .AddProject(projectId, "proj", "proj", LanguageNames.CSharp) + .AddMetadataReferences(projectId, TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)) + .AddDocument(documentId, "test.cs", SourceText.From("class C { }", Encoding.UTF8), filePath: "test.cs")); + + var solution = localWorkspace.CurrentSolution; + var project = solution.GetRequiredProject(projectId); + var document = solution.GetRequiredDocument(documentId); + var syntaxTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None); + + var sessionState = localWorkspace.GetService(); + var diagnosticRefresher = localWorkspace.GetService(); + var observedDiagnosticVersion = diagnosticRefresher.GlobalStateVersion; + + // StartDebuggingSession + + var debuggingSession = mockEncService.StartDebuggingSessionImpl = (_, _, _, _, _, _) => new DebuggingSessionId(1); + + Assert.False(sessionState.IsSessionActive); + Assert.Empty(sessionState.ApplyChangesDiagnostics); + + await localService.StartSessionAsync(CancellationToken.None); + + Assert.True(sessionState.IsSessionActive); + Assert.Empty(sessionState.ApplyChangesDiagnostics); + + // EnterBreakStateAsync + + mockEncService.BreakStateOrCapabilitiesChangedImpl = (bool? inBreakState) => + { + Assert.True(inBreakState); + }; + + await localService.EnterBreakStateAsync(CancellationToken.None); + + Assert.Equal(++observedDiagnosticVersion, diagnosticRefresher.GlobalStateVersion); + Assert.Empty(sessionState.ApplyChangesDiagnostics); + Assert.True(sessionState.IsSessionActive); + + // EmitSolutionUpdate + + var diagnosticDescriptor1 = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile); + + mockEncService.EmitSolutionUpdateImpl = (solution, _) => + { + var syntaxTree = solution.GetRequiredDocument(documentId).GetSyntaxTreeSynchronously(CancellationToken.None)!; + + var documentDiagnostic = Diagnostic.Create(diagnosticDescriptor1, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)), ["doc", "error 1"]); + var projectDiagnostic = Diagnostic.Create(diagnosticDescriptor1, Location.None, ["proj", "error 2"]); + var syntaxError = Diagnostic.Create(diagnosticDescriptor1, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)), ["doc", "syntax error 3"]); + + return new() + { + ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Ready, []), + Diagnostics = [new ProjectDiagnostics(project.Id, [documentDiagnostic, projectDiagnostic])], + RudeEdits = [(documentId, [new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(2, 3), arguments: ["x"])])], + SyntaxError = syntaxError + }; + }; + + var updates = await localService.GetUpdatesAsync(CancellationToken.None); + + Assert.Equal(++observedDiagnosticVersion, diagnosticRefresher.GlobalStateVersion); + + AssertEx.Equal( + [ + $"Error ENC1001: test.cs(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", + $"Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}" + ], sessionState.ApplyChangesDiagnostics.Select(Inspect)); + + AssertEx.Equal( + [ + $"Error ENC1001: test.cs(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", + $"Error ENC1001: test.cs(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "syntax error 3")}", + $"RestartRequired ENC0033: test.cs(0, 2, 0, 3): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "x")}" + ], updates.Diagnostics.Select(Inspect)); + + Assert.True(sessionState.IsSessionActive); + + if (commitChanges) + { + // CommitUpdatesAsync + + var called = false; + mockEncService.CommitSolutionUpdateImpl = () => called = true; + await localService.CommitUpdatesAsync(CancellationToken.None); + Assert.True(called); + } + else + { + // DiscardUpdatesAsync + + var called = false; + mockEncService.DiscardSolutionUpdateImpl = () => called = true; + await localService.DiscardUpdatesAsync(CancellationToken.None); + Assert.True(called); + } + + Assert.True(sessionState.IsSessionActive); + + // EndSessionAsync + + await localService.EndSessionAsync(CancellationToken.None); + + Assert.Equal(++observedDiagnosticVersion, diagnosticRefresher.GlobalStateVersion); + Assert.Empty(sessionState.ApplyChangesDiagnostics); + Assert.False(sessionState.IsSessionActive); + } + } +} diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index 9fa1c5ad7ca3a..4f0e0ea175216 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -24,7 +24,6 @@ using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api; using Microsoft.CodeAnalysis.ExternalAccess.Watch.Api; @@ -216,42 +215,27 @@ private async Task StartDebuggingSessionAsync( private void EnterBreakState( DebuggingSession session, - ImmutableArray activeStatements = default, - ImmutableArray documentsWithRudeEdits = default) + ImmutableArray activeStatements = default) { _debuggerService.GetActiveStatementsImpl = () => activeStatements.NullToEmpty(); - session.BreakStateOrCapabilitiesChanged(inBreakState: true, out var documentsToReanalyze); - AssertEx.Equal(documentsWithRudeEdits.NullToEmpty(), documentsToReanalyze); + session.BreakStateOrCapabilitiesChanged(inBreakState: true); } private void ExitBreakState( - DebuggingSession session, - ImmutableArray documentsWithRudeEdits = default) + DebuggingSession session) { _debuggerService.GetActiveStatementsImpl = () => ImmutableArray.Empty; - session.BreakStateOrCapabilitiesChanged(inBreakState: false, out var documentsToReanalyze); - AssertEx.Equal(documentsWithRudeEdits.NullToEmpty(), documentsToReanalyze); + session.BreakStateOrCapabilitiesChanged(inBreakState: false); } - private static void CapabilitiesChanged( - DebuggingSession session, - ImmutableArray documentsWithRudeEdits = default) - { - session.BreakStateOrCapabilitiesChanged(inBreakState: null, out var documentsToReanalyze); - AssertEx.Equal(documentsWithRudeEdits.NullToEmpty(), documentsToReanalyze); - } + private static void CapabilitiesChanged(DebuggingSession session) + => session.BreakStateOrCapabilitiesChanged(inBreakState: null); - private static void CommitSolutionUpdate(DebuggingSession session, ImmutableArray documentsWithRudeEdits = default) - { - session.CommitSolutionUpdate(out var documentsToReanalyze); - AssertEx.Equal(documentsWithRudeEdits.NullToEmpty(), documentsToReanalyze); - } + private static void CommitSolutionUpdate(DebuggingSession session) + => session.CommitSolutionUpdate(); - private static void EndDebuggingSession(DebuggingSession session, ImmutableArray documentsWithRudeEdits = default) - { - session.EndSession(out var documentsToReanalyze, out _); - AssertEx.Equal(documentsWithRudeEdits.NullToEmpty(), documentsToReanalyze); - } + private static void EndDebuggingSession(DebuggingSession session) + => session.EndSession(out _); private static async Task<(ModuleUpdates updates, ImmutableArray diagnostics)> EmitSolutionUpdateAsync( DebuggingSession session, @@ -1419,12 +1403,12 @@ public async Task RudeEdits(bool breakMode) if (breakMode) { - ExitBreakState(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(document2.Id)); + ExitBreakState(debuggingSession); EndDebuggingSession(debuggingSession); } else { - EndDebuggingSession(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(document2.Id)); + EndDebuggingSession(debuggingSession); } AssertEx.SetEqual(new[] { moduleId }, debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate()); @@ -1483,7 +1467,7 @@ public async Task DeferredApplyChangeWithActiveStatementRudeEdits() diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); // exit break state without applying the change: - ExitBreakState(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(document2.Id)); + ExitBreakState(debuggingSession); diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics); @@ -1497,7 +1481,7 @@ public async Task DeferredApplyChangeWithActiveStatementRudeEdits() diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); // exit break state without applying the change: - ExitBreakState(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(document2.Id)); + ExitBreakState(debuggingSession); // apply the change: var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); @@ -1554,7 +1538,7 @@ class C { int Y => 2; } Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); - EndDebuggingSession(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(generatedDocument.Id)); + EndDebuggingSession(debuggingSession); } [Theory] @@ -1632,12 +1616,12 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) if (breakMode) { - ExitBreakState(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(document2.Id)); + ExitBreakState(debuggingSession); EndDebuggingSession(debuggingSession); } else { - EndDebuggingSession(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(document2.Id)); + EndDebuggingSession(debuggingSession); } AssertEx.SetEqual(new[] { moduleId }, debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate()); @@ -1703,7 +1687,7 @@ public async Task RudeEdits_DocumentWithoutSequencePoints() Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); - EndDebuggingSession(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(document2.Id)); + EndDebuggingSession(debuggingSession); } [Fact] @@ -1760,7 +1744,7 @@ public async Task RudeEdits_DelayLoadedModule() Assert.Empty(updates.Updates); Assert.Empty(emitDiagnostics); - EndDebuggingSession(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(document2.Id)); + EndDebuggingSession(debuggingSession); } [Fact] @@ -2087,8 +2071,13 @@ public async Task HasChanges_Documents(DocumentKind documentKind) [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1371694")] public async Task Project_Add() { + // Project A: var sourceA1 = "class A { void M() { System.Console.WriteLine(1); } }"; + + // Project B (baseline, but not loaded into solution): var sourceB1 = "class B { int F() => 1; }"; + + // Additional documents added to B: var sourceB2 = "class B { int G() => 1; }"; var sourceB3 = "class B { int F() => 2; }"; @@ -2141,7 +2130,7 @@ public async Task Project_Add() }, baseSpans.Select(spans => spans.IsEmpty ? "" : string.Join(",", spans.Select(s => s.LineSpan.ToString())))); var trackedActiveSpans = ImmutableArray.Create( - new ActiveStatementSpan(1, activeLineSpanB1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, unmappedDocumentId: null)); + new ActiveStatementSpan(new ActiveStatementId(0), activeLineSpanB1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame)); var currentSpans = await debuggingSession.GetAdjustedActiveStatementSpansAsync(documentB2, (_, _, _) => new(trackedActiveSpans), CancellationToken.None); // TODO: https://github.com/dotnet/roslyn/issues/1204 @@ -2226,7 +2215,7 @@ public async Task Capabilities(bool breakState) if (breakState) { - ExitBreakState(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(documentId)); + ExitBreakState(debuggingSession); } diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetDocument(documentId), s_noActiveSpans, CancellationToken.None); @@ -2238,11 +2227,11 @@ public async Task Capabilities(bool breakState) if (breakState) { - EnterBreakState(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(documentId)); + EnterBreakState(debuggingSession); } else { - CapabilitiesChanged(debuggingSession, documentsWithRudeEdits: ImmutableArray.Create(documentId)); + CapabilitiesChanged(debuggingSession); } diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetDocument(documentId), s_noActiveSpans, CancellationToken.None); @@ -2387,7 +2376,7 @@ public async Task ValidSignificantChange_EmitError() // no pending update: Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate()); - Assert.Throws(() => debuggingSession.CommitSolutionUpdate(out var _)); + Assert.Throws(() => debuggingSession.CommitSolutionUpdate()); Assert.Throws(() => debuggingSession.DiscardSolutionUpdate()); // no change in non-remappable regions since we didn't have any active statements: @@ -3591,8 +3580,8 @@ public async Task ActiveStatements() EnterBreakState(debuggingSession, activeStatements); - var activeStatementSpan11 = new ActiveStatementSpan(0, activeLineSpan11, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, unmappedDocumentId: null); - var activeStatementSpan12 = new ActiveStatementSpan(1, activeLineSpan12, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, unmappedDocumentId: null); + var activeStatementSpan11 = new ActiveStatementSpan(new ActiveStatementId(0), activeLineSpan11, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame); + var activeStatementSpan12 = new ActiveStatementSpan(new ActiveStatementId(1), activeLineSpan12, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame); var baseSpans = await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document1.Id), CancellationToken.None); AssertEx.Equal(new[] @@ -3611,8 +3600,8 @@ public async Task ActiveStatements() var document2 = solution.GetDocument(documentId); // tracking span update triggered by the edit: - var activeStatementSpan21 = new ActiveStatementSpan(0, activeLineSpan21, ActiveStatementFlags.NonLeafFrame, unmappedDocumentId: null); - var activeStatementSpan22 = new ActiveStatementSpan(1, activeLineSpan22, ActiveStatementFlags.LeafFrame, unmappedDocumentId: null); + var activeStatementSpan21 = new ActiveStatementSpan(new ActiveStatementId(0), activeLineSpan21, ActiveStatementFlags.NonLeafFrame); + var activeStatementSpan22 = new ActiveStatementSpan(new ActiveStatementId(1), activeLineSpan22, ActiveStatementFlags.LeafFrame); var trackedActiveSpans2 = ImmutableArray.Create(activeStatementSpan21, activeStatementSpan22); currentSpans = await debuggingSession.GetAdjustedActiveStatementSpansAsync(document2, (_, _, _) => new(trackedActiveSpans2), CancellationToken.None); @@ -3671,8 +3660,8 @@ public async Task ActiveStatements_SyntaxErrorOrOutOfSyncDocument(bool isOutOfSy var baseSpans = (await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single(); AssertEx.Equal(new[] { - new ActiveStatementSpan(0, activeLineSpan11, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, unmappedDocumentId: null), - new ActiveStatementSpan(1, activeLineSpan12, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, unmappedDocumentId: null) + new ActiveStatementSpan(new ActiveStatementId(0), activeLineSpan11, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame), + new ActiveStatementSpan(new ActiveStatementId(1), activeLineSpan12, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame) }, baseSpans); // change the source (valid edit): @@ -3801,7 +3790,7 @@ DocumentId AddProjectAndLinkDocument(string projectName, Document doc, SourceTex Assert.Equal(3, baseActiveStatementsMap.InstructionMap.Count); - var statements = baseActiveStatementsMap.InstructionMap.Values.OrderBy(v => v.Ordinal).ToArray(); + var statements = baseActiveStatementsMap.InstructionMap.Values.OrderBy(v => v.Id.Ordinal).ToArray(); var s = statements[0]; Assert.Equal(0x06000001, s.InstructionId.Method.Token); Assert.Equal(module4, s.InstructionId.Method.Module); @@ -4073,7 +4062,7 @@ int F() solution = solution.WithDocumentText(document.Id, CreateText(source1)); document = solution.GetDocument(document.Id); - ExitBreakState(debuggingSession, ImmutableArray.Create(document.Id)); + ExitBreakState(debuggingSession); // change the source (now a valid edit since there is no active statement) solution = solution.WithDocumentText(document.Id, CreateText(source2)); @@ -4197,7 +4186,7 @@ static void F() var spans = (await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single(); AssertEx.Equal(new[] { - new ActiveStatementSpan(0, new LinePositionSpan(new(4,41), new(4,42)), ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, unmappedDocumentId: null), + new ActiveStatementSpan(new ActiveStatementId(0), new LinePositionSpan(new(4,41), new(4,42)), ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame), }, spans); solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSourceV4))); @@ -4299,8 +4288,8 @@ static void F() var spans = (await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single(); AssertEx.Equal(new[] { - new ActiveStatementSpan(0, expectedSpanG1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, documentId), - new ActiveStatementSpan(1, expectedSpanF1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, documentId) + new ActiveStatementSpan(new ActiveStatementId(0), expectedSpanG1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, documentId), + new ActiveStatementSpan(new ActiveStatementId(1), expectedSpanF1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, documentId) }, spans); solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSource3))); @@ -4312,8 +4301,8 @@ static void F() spans = (await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single(); AssertEx.Equal(new[] { - new ActiveStatementSpan(0, expectedSpanG2, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, documentId), - new ActiveStatementSpan(1, expectedSpanF2, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, documentId) + new ActiveStatementSpan(new ActiveStatementId(0), expectedSpanG2, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, documentId), + new ActiveStatementSpan(new ActiveStatementId(1), expectedSpanF2, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, documentId) }, spans); // no rude edits: @@ -4408,7 +4397,7 @@ static void F() var spans = (await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single(); AssertEx.Equal(new[] { - new ActiveStatementSpan(0, expectedSpanG1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, unmappedDocumentId: null) + new ActiveStatementSpan(new ActiveStatementId(0), expectedSpanG1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame) // active statement in F has been deleted }, spans); @@ -4464,7 +4453,7 @@ public async Task MultiSession() Assert.Equal("CS0103", result2.Diagnostics.Single().Diagnostics.Single().Id); Assert.Empty(result2.ModuleUpdates.Updates); - encService.EndDebuggingSession(sessionId, out var _); + encService.EndDebuggingSession(sessionId); }); await Task.WhenAll(tasks); @@ -4484,10 +4473,10 @@ public async Task Disposal() // The folling methods shall not be called after the debugging session ended. await Assert.ThrowsAsync(async () => await debuggingSession.EmitSolutionUpdateAsync(solution, s_noActiveSpans, CancellationToken.None)); - Assert.Throws(() => debuggingSession.BreakStateOrCapabilitiesChanged(inBreakState: true, out _)); + Assert.Throws(() => debuggingSession.BreakStateOrCapabilitiesChanged(inBreakState: true)); Assert.Throws(() => debuggingSession.DiscardSolutionUpdate()); - Assert.Throws(() => debuggingSession.CommitSolutionUpdate(out _)); - Assert.Throws(() => debuggingSession.EndSession(out _, out _)); + Assert.Throws(() => debuggingSession.CommitSolutionUpdate()); + Assert.Throws(() => debuggingSession.EndSession(out _)); // The following methods can be called at any point in time, so we must handle race with dispose gracefully. Assert.Empty(await debuggingSession.GetDocumentDiagnosticsAsync(document, s_noActiveSpans, CancellationToken.None)); diff --git a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs index 91df0b1a8e82d..ee50eaacbdb1f 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs @@ -10,14 +10,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests; -using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; @@ -39,42 +36,40 @@ private static string Inspect(DiagnosticData d) (!string.IsNullOrWhiteSpace(d.DataLocation.UnmappedFileSpan.Path) ? $" {d.DataLocation.UnmappedFileSpan.Path}({d.DataLocation.UnmappedFileSpan.StartLinePosition.Line}, {d.DataLocation.UnmappedFileSpan.StartLinePosition.Character}, {d.DataLocation.UnmappedFileSpan.EndLinePosition.Line}, {d.DataLocation.UnmappedFileSpan.EndLinePosition.Character}):" : "") + $" {d.Message}"; - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Proxy(TestHost testHost) { var localComposition = EditorTestCompositions.EditorFeatures.WithTestHostParts(testHost) - .AddExcludedPartTypes(typeof(DiagnosticAnalyzerService)) - .AddParts(typeof(MockDiagnosticAnalyzerService), typeof(NoCompilationLanguageService)); + .AddParts(typeof(NoCompilationLanguageService)); if (testHost == TestHost.InProcess) { localComposition = localComposition .AddExcludedPartTypes(typeof(EditAndContinueService)) - .AddParts(typeof(MockEditAndContinueWorkspaceService)); + .AddParts(typeof(MockEditAndContinueService)); } using var localWorkspace = new TestWorkspace(composition: localComposition); var globalOptions = localWorkspace.GetService(); - MockEditAndContinueWorkspaceService mockEncService; + MockEditAndContinueService mockEncService; var clientProvider = (InProcRemoteHostClientProvider?)localWorkspace.Services.GetService(); if (testHost == TestHost.InProcess) { Assert.Null(clientProvider); - mockEncService = (MockEditAndContinueWorkspaceService)localWorkspace.GetService(); + mockEncService = (MockEditAndContinueService)localWorkspace.GetService(); } else { Assert.NotNull(clientProvider); - clientProvider!.AdditionalRemoteParts = [typeof(MockEditAndContinueWorkspaceService)]; + clientProvider!.AdditionalRemoteParts = [typeof(MockEditAndContinueService)]; clientProvider!.ExcludedRemoteParts = [typeof(EditAndContinueService)]; var client = await InProcRemoteHostClient.GetTestClientAsync(localWorkspace); var remoteWorkspace = client.TestData.WorkspaceManager.GetWorkspace(); - mockEncService = (MockEditAndContinueWorkspaceService)remoteWorkspace.Services.GetRequiredService().Service; + mockEncService = (MockEditAndContinueService)remoteWorkspace.Services.GetRequiredService().Service; } var projectId = ProjectId.CreateNewId(); @@ -96,20 +91,6 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution var inProcOnlyDocument = solution.GetRequiredDocument(inProcOnlyDocumentId); var syntaxTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None); - var mockDiagnosticService = (MockDiagnosticAnalyzerService)localWorkspace.GetService(); - - void VerifyReanalyzeInvocation(ImmutableArray documentIds) - { - AssertEx.Equal(documentIds, mockDiagnosticService.DocumentsToReanalyze); - mockDiagnosticService.DocumentsToReanalyze.Clear(); - } - - var diagnosticUpdateSource = new EditAndContinueDiagnosticUpdateSource(); - var emitDiagnosticsUpdated = new List(); - var emitDiagnosticsClearedCount = 0; - diagnosticUpdateSource.DiagnosticsUpdated += (object sender, ImmutableArray args) => emitDiagnosticsUpdated.AddRange(args); - diagnosticUpdateSource.DiagnosticsCleared += (object sender, EventArgs args) => emitDiagnosticsClearedCount++; - var span1 = new LinePositionSpan(new LinePosition(1, 2), new LinePosition(1, 5)); var moduleId1 = new Guid("{44444444-1111-1111-1111-111111111111}"); var methodId1 = new ManagedMethodId(moduleId1, token: 0x06000003, version: 2); @@ -129,7 +110,7 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) newSpan: new SourceSpan(1, 2, 1, 5)); var activeSpans1 = ImmutableArray.Create( - new ActiveStatementSpan(0, new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4)), ActiveStatementFlags.NonLeafFrame, documentId)); + new ActiveStatementSpan(new ActiveStatementId(0), new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4)), ActiveStatementFlags.NonLeafFrame, documentId)); var activeStatementSpanProvider = new ActiveStatementSpanProvider((documentId, path, cancellationToken) => { @@ -141,7 +122,7 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) var diagnosticDescriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.AddingTypeRuntimeCapabilityRequired); var diagnostic = Diagnostic.Create(diagnosticDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 1))); - var proxy = new RemoteEditAndContinueServiceProxy(localWorkspace); + var proxy = new RemoteEditAndContinueServiceProxy(solution.Services); // StartDebuggingSession @@ -175,17 +156,12 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) // BreakStateChanged - mockEncService.BreakStateOrCapabilitiesChangedImpl = (bool? inBreakState, out ImmutableArray documentsToReanalyze) => + mockEncService.BreakStateOrCapabilitiesChangedImpl = (bool? inBreakState) => { Assert.True(inBreakState); - documentsToReanalyze = ImmutableArray.Create(documentId); }; - await sessionProxy.BreakStateOrCapabilitiesChangedAsync(mockDiagnosticService, diagnosticUpdateSource, inBreakState: true, CancellationToken.None); - VerifyReanalyzeInvocation(ImmutableArray.Create(documentId)); - - Assert.Equal(1, emitDiagnosticsClearedCount); - emitDiagnosticsClearedCount = 0; + await sessionProxy.BreakStateOrCapabilitiesChangedAsync(inBreakState: true, CancellationToken.None); var activeStatement = (await remoteDebuggeeModuleMetadataProvider!.GetActiveStatementsAsync(CancellationToken.None)).Single(); Assert.Equal(as1.ActiveInstruction, activeStatement.ActiveInstruction); @@ -237,24 +213,11 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) }; }; - var (updates, _, _, syntaxErrorData) = await sessionProxy.EmitSolutionUpdateAsync(localWorkspace.CurrentSolution, activeStatementSpanProvider, mockDiagnosticService, diagnosticUpdateSource, CancellationToken.None); + var (updates, _, _, syntaxErrorData) = await sessionProxy.EmitSolutionUpdateAsync(localWorkspace.CurrentSolution, activeStatementSpanProvider, CancellationToken.None); AssertEx.Equal($"[{projectId}] Error ENC1001: test.cs(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "syntax error")}", Inspect(syntaxErrorData!)); - VerifyReanalyzeInvocation(ImmutableArray.Create(documentId)); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.Equal(1, emitDiagnosticsClearedCount); - emitDiagnosticsClearedCount = 0; - - AssertEx.Equal(new[] - { - $"[{projectId}] Error ENC1001: test.cs(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "some error")}", - $"[{projectId}] Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "proj", "some error")}" - }, emitDiagnosticsUpdated.Select(update => Inspect(update.Diagnostics.Single()))); - - emitDiagnosticsUpdated.Clear(); - var delta = updates.Updates.Single(); Assert.Equal(moduleId1, delta.Module); AssertEx.Equal(new byte[] { 1, 2 }, delta.ILDelta); @@ -275,13 +238,7 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) // CommitSolutionUpdate - mockEncService.CommitSolutionUpdateImpl = (out ImmutableArray documentsToReanalyze) => - { - documentsToReanalyze = ImmutableArray.Create(documentId); - }; - - await sessionProxy.CommitSolutionUpdateAsync(mockDiagnosticService, CancellationToken.None); - VerifyReanalyzeInvocation(ImmutableArray.Create(documentId)); + await sessionProxy.CommitSolutionUpdateAsync(CancellationToken.None); // DiscardSolutionUpdate @@ -292,7 +249,7 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) // GetBaseActiveStatementSpans - var activeStatementSpan1 = new ActiveStatementSpan(0, span1, ActiveStatementFlags.NonLeafFrame | ActiveStatementFlags.PartiallyExecuted, unmappedDocumentId: documentId); + var activeStatementSpan1 = new ActiveStatementSpan(new ActiveStatementId(0), span1, ActiveStatementFlags.NonLeafFrame | ActiveStatementFlags.PartiallyExecuted, UnmappedDocumentId: documentId); mockEncService.GetBaseActiveStatementSpansImpl = (solution, documentIds) => { @@ -328,21 +285,12 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) mockEncService.GetDocumentDiagnosticsImpl = (document, activeStatementProvider) => ImmutableArray.Create(diagnostic); - Assert.Empty(await proxy.GetDocumentDiagnosticsAsync(inProcOnlyDocument, inProcOnlyDocument, activeStatementSpanProvider, CancellationToken.None)); - Assert.Equal(diagnostic.GetMessage(), (await proxy.GetDocumentDiagnosticsAsync(document, document, activeStatementSpanProvider, CancellationToken.None)).Single().GetMessage()); + Assert.Empty(await proxy.GetDocumentDiagnosticsAsync(inProcOnlyDocument, activeStatementSpanProvider, CancellationToken.None)); + Assert.Equal(diagnostic.GetMessage(), (await proxy.GetDocumentDiagnosticsAsync(document, activeStatementSpanProvider, CancellationToken.None)).Single().Message); // EndDebuggingSession - mockEncService.EndDebuggingSessionImpl = (out ImmutableArray documentsToReanalyze) => - { - documentsToReanalyze = ImmutableArray.Create(documentId); - }; - - await sessionProxy.EndDebuggingSessionAsync(solution, diagnosticUpdateSource, mockDiagnosticService, CancellationToken.None); - VerifyReanalyzeInvocation(ImmutableArray.Create(documentId)); - Assert.Equal(1, emitDiagnosticsClearedCount); - emitDiagnosticsClearedCount = 0; - Assert.Empty(emitDiagnosticsUpdated); + await sessionProxy.EndDebuggingSessionAsync(CancellationToken.None); } } } diff --git a/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs b/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs index ca2b47721d8d9..434fcf3f2cd69 100644 --- a/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs +++ b/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs @@ -61,7 +61,7 @@ public void AssemblyAndPdb(DebugInformationFormat format) Stream? currentPdbStream = null; var outputs = new TestCompilationOutputs( - openAssemblyStream: () => currentPEStream = new MemoryStream(peImage.ToArray()), + openAssemblyStream: () => currentPEStream = new MemoryStream([.. peImage]), openPdbStream: () => { if (pdbStream == null) diff --git a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs index 3afca1874b065..8044e56df5a09 100644 --- a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs +++ b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs @@ -44,7 +44,7 @@ public static TestContext Create( string? metadataCommonReferences = null) { projectLanguage ??= LanguageNames.CSharp; - metadataSources ??= SpecializedCollections.EmptyEnumerable(); + metadataSources ??= []; metadataSources = !metadataSources.Any() ? new[] { AbstractMetadataAsSourceTests.DefaultMetadataSource } : metadataSources; diff --git a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs index ef3aa253d2d2f..8ec8dcaf5894b 100644 --- a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs +++ b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs @@ -25,9 +25,9 @@ public abstract partial class AbstractMetadataAsSourceTests : IAsyncLifetime public virtual Task InitializeAsync() { - AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.MscorlibRef_v46, "mscorlib.v4_6_1038_0.dll", ImmutableArray.Create(Net461.References.mscorlib.ImageBytes)); - AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.SystemRef_v46, "System.v4_6_1038_0.dll", ImmutableArray.Create(Net461.References.System.ImageBytes)); - AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.SystemCoreRef_v46, "System.Core.v4_6_1038_0.dll", ImmutableArray.Create(Net461.References.SystemCore.ImageBytes)); + AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.MscorlibRef_v46, "mscorlib.v4_6_1038_0.dll", ImmutableArray.Create(Net461.ReferenceInfos.mscorlib.ImageBytes)); + AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.SystemRef_v46, "System.v4_6_1038_0.dll", ImmutableArray.Create(Net461.ReferenceInfos.System.ImageBytes)); + AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.SystemCoreRef_v46, "System.Core.v4_6_1038_0.dll", ImmutableArray.Create(Net461.ReferenceInfos.SystemCore.ImageBytes)); AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.ValueTupleRef, "System.ValueTuple.dll", ImmutableArray.Create(TestResources.NetFX.ValueTuple.tuplelib)); AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.SystemRuntimeFacadeRef, "System.Runtime.dll", ImmutableArray.Create(TestMetadata.ResourcesNet451.SystemRuntime)); AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.MsvbRef, "Microsoft.VisualBasic.dll", ImmutableArray.Create(TestMetadata.ResourcesNet451.MicrosoftVisualBasic)); @@ -47,7 +47,7 @@ public virtual Task DisposeAsync() internal static async Task GenerateAndVerifySourceAsync( string metadataSource, string symbolName, string projectLanguage, string expected, bool signaturesOnly = true, bool includeXmlDocComments = false, string languageVersion = null, string metadataLanguageVersion = null, string metadataCommonReferences = null) { - using var context = TestContext.Create(projectLanguage, SpecializedCollections.SingletonEnumerable(metadataSource), includeXmlDocComments, languageVersion: languageVersion, metadataLanguageVersion: metadataLanguageVersion, metadataCommonReferences: metadataCommonReferences); + using var context = TestContext.Create(projectLanguage, [metadataSource], includeXmlDocComments, languageVersion: languageVersion, metadataLanguageVersion: metadataLanguageVersion, metadataCommonReferences: metadataCommonReferences); await context.GenerateAndVerifySourceAsync(symbolName, expected, signaturesOnly: signaturesOnly); } @@ -103,7 +103,7 @@ internal static async Task TestSymbolIdMatchesMetadataAsync(string projectLangua var metadataSource = @"[assembly: System.Reflection.AssemblyVersion(""2.0.0.0"")] public class C { }"; var symbolName = "C"; - using var context = TestContext.Create(projectLanguage, SpecializedCollections.SingletonEnumerable(metadataSource)); + using var context = TestContext.Create(projectLanguage, [metadataSource]); var metadataSymbol = await context.ResolveSymbolAsync(symbolName); var metadataSymbolId = metadataSymbol.GetSymbolKey(); var generatedFile = await context.GenerateSourceAsync(symbolName); diff --git a/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs b/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs index a73a79f37e118..214c1ed2275cd 100644 --- a/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs +++ b/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs @@ -976,7 +976,7 @@ public async Task TestTypeInFileScopedNamespace1() var metadataSource = "namespace N { public class C {} }"; using var context = TestContext.Create( - LanguageNames.CSharp, SpecializedCollections.SingletonEnumerable(metadataSource), languageVersion: "10"); + LanguageNames.CSharp, [metadataSource], languageVersion: "10"); await context.GenerateAndVerifySourceAsync("N.C", $@"#region {FeaturesResources.Assembly} ReferencedAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null @@ -998,7 +998,7 @@ public async Task TestTypeInFileScopedNamespace2() var metadataSource = "namespace N { public class C {} }"; using var context = TestContext.Create( - LanguageNames.CSharp, SpecializedCollections.SingletonEnumerable(metadataSource), languageVersion: "9"); + LanguageNames.CSharp, [metadataSource], languageVersion: "9"); await context.GenerateAndVerifySourceAsync("N.C", $@"#region {FeaturesResources.Assembly} ReferencedAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null @@ -1020,7 +1020,7 @@ public async Task TestTypeInFileScopedNamespace3() var metadataSource = "namespace N { public class C {} }"; using var context = TestContext.Create( - LanguageNames.CSharp, SpecializedCollections.SingletonEnumerable(metadataSource), languageVersion: "10"); + LanguageNames.CSharp, [metadataSource], languageVersion: "10"); await context.GenerateAndVerifySourceAsync("N.C", $@"#region {FeaturesResources.Assembly} ReferencedAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null @@ -1699,7 +1699,7 @@ public async Task TestReuseGenerateMemberOfGeneratedType() { var metadataSource = "public class C { public bool Is; }"; - using var context = TestContext.Create(LanguageNames.CSharp, SpecializedCollections.SingletonEnumerable(metadataSource)); + using var context = TestContext.Create(LanguageNames.CSharp, [metadataSource]); var a = await context.GenerateSourceAsync("C"); var b = await context.GenerateSourceAsync("C.Is"); TestContext.VerifyDocumentReused(a, b); @@ -2693,7 +2693,7 @@ public static class ObjectExtensions using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, sourceWithSymbolReference: sourceWithSymbolReference); var navigationSymbol = await context.GetNavigationSymbolAsync(); @@ -2735,7 +2735,7 @@ End Module using var context = TestContext.Create( LanguageNames.VisualBasic, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, sourceWithSymbolReference: sourceWithSymbolReference); var navigationSymbol = await context.GetNavigationSymbolAsync(); @@ -3199,7 +3199,7 @@ public class [|TestType|] where T : unmanaged using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "7.3", sourceWithSymbolReference: sourceWithSymbolReference); @@ -3239,7 +3239,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "7.3", sourceWithSymbolReference: sourceWithSymbolReference); @@ -3268,7 +3268,7 @@ class C using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "7.3", sourceWithSymbolReference: sourceWithSymbolReference); @@ -4746,7 +4746,7 @@ public class [|TestType|] where T : notnull using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -4788,7 +4788,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -4819,7 +4819,7 @@ class C using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -4877,7 +4877,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -4932,7 +4932,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -4995,7 +4995,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5045,7 +5045,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5096,7 +5096,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5144,7 +5144,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5192,7 +5192,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5238,7 +5238,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5284,7 +5284,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5330,7 +5330,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5374,7 +5374,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5438,7 +5438,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5512,7 +5512,7 @@ public class Nested using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, @@ -5556,7 +5556,7 @@ public class TestType using var context = TestContext.Create( LanguageNames.CSharp, - SpecializedCollections.SingletonEnumerable(metadata), + [metadata], includeXmlDocComments: false, languageVersion: "8", sourceWithSymbolReference: sourceWithSymbolReference, diff --git a/src/EditorFeatures/Test/Options/GlobalOptionsTests.cs b/src/EditorFeatures/Test/Options/GlobalOptionsTests.cs index fecd138428d1e..9de1873576955 100644 --- a/src/EditorFeatures/Test/Options/GlobalOptionsTests.cs +++ b/src/EditorFeatures/Test/Options/GlobalOptionsTests.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -14,13 +13,11 @@ using Microsoft.CodeAnalysis.BraceMatching; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.DocumentationComments; +using Microsoft.CodeAnalysis.DocumentHighlighting; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.UnitTests; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.ExtractMethod; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Formatting; @@ -165,7 +162,8 @@ private static bool IsStoredInGlobalOptions(PropertyInfo property, string? langu property.DeclaringType == typeof(AddImportPlacementOptions) && property.Name == nameof(AddImportPlacementOptions.UsingDirectivePlacement) && language == LanguageNames.VisualBasic || property.DeclaringType == typeof(DocumentFormattingOptions) && property.Name == nameof(DocumentFormattingOptions.FileHeaderTemplate) || property.DeclaringType == typeof(DocumentFormattingOptions) && property.Name == nameof(DocumentFormattingOptions.InsertFinalNewLine) || - property.DeclaringType == typeof(ClassificationOptions) && property.Name == nameof(ClassificationOptions.ForceFrozenPartialSemanticsForCrossProcessOperations) || + property.DeclaringType == typeof(ClassificationOptions) && property.Name == nameof(ClassificationOptions.FrozenPartialSemantics) || + property.DeclaringType == typeof(HighlightingOptions) && property.Name == nameof(HighlightingOptions.FrozenPartialSemantics) || property.DeclaringType == typeof(BlockStructureOptions) && property.Name == nameof(BlockStructureOptions.IsMetadataAsSource)); /// diff --git a/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs b/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs index be8daacdf0597..e5c4e0b27f424 100644 --- a/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs +++ b/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs @@ -125,7 +125,7 @@ public async Task TestPreviewServices() using var previewWorkspace = new PreviewWorkspace(EditorTestCompositions.EditorFeatures.GetHostServices()); var persistentService = previewWorkspace.Services.SolutionServices.GetPersistentStorageService(); - await using var storage = await persistentService.GetStorageAsync(SolutionKey.ToSolutionKey(previewWorkspace.CurrentSolution), CancellationToken.None); + var storage = await persistentService.GetStorageAsync(SolutionKey.ToSolutionKey(previewWorkspace.CurrentSolution), CancellationToken.None); Assert.IsType(storage); } diff --git a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs index 8211597d100e1..299725f22611d 100644 --- a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs +++ b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs @@ -112,7 +112,7 @@ public RenameTrackingTestState( { _codeRefactoringProvider = new RenameTrackingCodeRefactoringProvider( _historyRegistry, - SpecializedCollections.SingletonEnumerable(_mockRefactorNotifyService)); + [_mockRefactorNotifyService]); } else { diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index f8fe60b663d18..335fef3fef2df 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -515,7 +515,7 @@ private static ImmutableArray GetSnippetPlaceholders(string } } - return arrayBuilder.ToImmutable(); + return arrayBuilder.ToImmutableAndClear(); } } } diff --git a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs index 42046b5d37d1c..00ceef681f06d 100644 --- a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs +++ b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs @@ -31,7 +31,8 @@ public class StructureTaggerTests public async Task CSharpOutliningTagger( bool collapseRegionsWhenCollapsingToDefinitions, bool showBlockStructureGuidesForDeclarationLevelConstructs, - bool showBlockStructureGuidesForCodeLevelConstructs) + bool showBlockStructureGuidesForCodeLevelConstructs, + bool showBlockStructureGuidesForCommentsAndPreprocessorRegions) { var code = @"using System; @@ -59,6 +60,7 @@ static void Main(string[] args) globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions); globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForDeclarationLevelConstructs); globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForCodeLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, LanguageNames.CSharp, showBlockStructureGuidesForCommentsAndPreprocessorRegions); var tags = await GetTagsFromWorkspaceAsync(workspace); @@ -74,7 +76,7 @@ static void Main(string[] args) { Assert.Equal(collapseRegionsWhenCollapsingToDefinitions, regionTag.IsImplementation); Assert.Equal(14, GetCollapsedHintLineCount(regionTag)); - Assert.Equal(PredefinedStructureTagTypes.Nonstructural, regionTag.Type); + Assert.Equal(showBlockStructureGuidesForCommentsAndPreprocessorRegions ? PredefinedStructureTagTypes.PreprocessorRegion : PredefinedStructureTagTypes.Nonstructural, regionTag.Type); Assert.Equal("#region MyRegion", GetHeaderText(regionTag)); }, classTag => @@ -334,10 +336,10 @@ private static async Task> GetTagsFromWorkspaceAsyn var provider = workspace.ExportProvider.GetExportedValue(); var document = workspace.CurrentSolution.GetDocument(hostdoc.Id); - var context = new TaggerContext(document, view.TextSnapshot); + var context = new TaggerContext(document, view.TextSnapshot, frozenPartialSemantics: false); await provider.GetTestAccessor().ProduceTagsAsync(context); - return context.TagSpans.Select(x => x.Tag).OrderBy(t => t.OutliningSpan.Value.Start).ToList(); + return [.. context.TagSpans.Select(x => x.Tag).OrderBy(t => t.OutliningSpan.Value.Start)]; } #pragma warning restore CS0618 // Type or member is obsolete diff --git a/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs b/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs index 02355240a236d..6833e685ded8c 100644 --- a/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs +++ b/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs @@ -12,8 +12,8 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.Editor.UnitTests; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -66,14 +66,15 @@ void M() WpfTestRunner.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(LargeNumberOfSpans)} creates asynchronous taggers"); - var eventSource = CreateEventSource(); + var eventSource = new TestTaggerEventSource(); var taggerProvider = new TestTaggerProvider( workspace.GetService(), - (s, c) => Enumerable + (_, s) => Enumerable .Range(0, tagsProduced) - .Select(i => new TagSpan(new SnapshotSpan(s.Snapshot, new Span(50 + i * 2, 1)), new TextMarkerTag($"Test{i}"))), + .Select(i => new TagSpan(new SnapshotSpan(s.SnapshotSpan.Snapshot, new Span(50 + i * 2, 1)), new TextMarkerTag($"Test{i}"))), eventSource, workspace.GetService(), + supportsFrozenPartialSemantics: false, asyncListener); var document = workspace.Documents.First(); @@ -141,27 +142,130 @@ class Program Assert.Equal(2, tags.Count()); } - private static TestTaggerEventSource CreateEventSource() - => new(); + [WpfFact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2016199")] + public async Task TestFrozenPartialSemantics1() + { + using var workspace = EditorTestWorkspace.CreateCSharp(""" + class Program + { + } + """); + + var asyncListener = new AsynchronousOperationListener(); + + WpfTestRunner.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(TestFrozenPartialSemantics1)} creates asynchronous taggers"); + + var testDocument = workspace.Documents.First(); + + var eventSource = new TestTaggerEventSource(); + var callbackCounter = 0; + var taggerProvider = new TestTaggerProvider( + workspace.GetService(), + (c, s) => + { + Assert.True(callbackCounter <= 1); + var document = workspace.CurrentSolution.GetRequiredDocument(testDocument.Id); + if (callbackCounter is 0) + { + Assert.True(c.FrozenPartialSemantics); + // Should be getting a frozen document here. + Assert.NotSame(document, c.SpansToTag.First().Document); + } + else + { + Assert.False(c.FrozenPartialSemantics); + Assert.Same(document, c.SpansToTag.First().Document); + } + + callbackCounter++; + return [new TagSpan(new SnapshotSpan(s.SnapshotSpan.Snapshot, new Span(0, 1)), new TextMarkerTag($"Test"))]; + }, + eventSource, + workspace.GetService(), + supportsFrozenPartialSemantics: true, + asyncListener); + + var textBuffer = testDocument.GetTextBuffer(); + using var tagger = taggerProvider.CreateTagger(textBuffer); + Contract.ThrowIfNull(tagger); + + eventSource.SendUpdateEvent(); + + await asyncListener.ExpeditedWaitAsync(); + Assert.Equal(2, callbackCounter); + + var tags = tagger.GetTags(new NormalizedSnapshotSpanCollection(textBuffer.CurrentSnapshot.GetFullSpan())); + Assert.Equal(1, tags.Count()); + } + + [WpfFact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2016199")] + public async Task TestFrozenPartialSemantics2() + { + using var workspace = EditorTestWorkspace.CreateCSharp(""" + class Program + { + } + """); + + var asyncListener = new AsynchronousOperationListener(); + + WpfTestRunner.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(TestFrozenPartialSemantics2)} creates asynchronous taggers"); + + var testDocument = workspace.Documents.First(); + + var eventSource = new TestTaggerEventSource(); + var callbackCounter = 0; + var taggerProvider = new TestTaggerProvider( + workspace.GetService(), + (c, s) => + { + var document = workspace.CurrentSolution.GetRequiredDocument(testDocument.Id); + Assert.True(callbackCounter == 0); + Assert.False(c.FrozenPartialSemantics); + Assert.Same(document, c.SpansToTag.First().Document); + + callbackCounter++; + return [new TagSpan(new SnapshotSpan(s.SnapshotSpan.Snapshot, new Span(0, 1)), new TextMarkerTag($"Test"))]; + }, + eventSource, + workspace.GetService(), + supportsFrozenPartialSemantics: false, + asyncListener); + + var textBuffer = testDocument.GetTextBuffer(); + using var tagger = taggerProvider.CreateTagger(textBuffer); + Contract.ThrowIfNull(tagger); + + eventSource.SendUpdateEvent(); + + await asyncListener.ExpeditedWaitAsync(); + Assert.Equal(1, callbackCounter); + + var tags = tagger.GetTags(new NormalizedSnapshotSpanCollection(textBuffer.CurrentSnapshot.GetFullSpan())); + Assert.Equal(1, tags.Count()); + } private sealed class TestTaggerProvider( IThreadingContext threadingContext, - Func>> callback, + Func, DocumentSnapshotSpan, IEnumerable>> callback, ITaggerEventSource eventSource, IGlobalOptionService globalOptions, + bool supportsFrozenPartialSemantics, IAsynchronousOperationListener asyncListener) : AsynchronousTaggerProvider(threadingContext, globalOptions, visibilityTracker: null, asyncListener) { protected override TaggerDelay EventChangeDelay => TaggerDelay.NearImmediate; + protected override bool SupportsFrozenPartialSemantics => supportsFrozenPartialSemantics; + protected override ITaggerEventSource CreateEventSource(ITextView? textView, ITextBuffer subjectBuffer) => eventSource; protected override Task ProduceTagsAsync( TaggerContext context, DocumentSnapshotSpan snapshotSpan, int? caretPosition, CancellationToken cancellationToken) { - foreach (var tag in callback(snapshotSpan.SnapshotSpan, cancellationToken)) + foreach (var tag in callback(context, snapshotSpan)) context.AddTag(tag); return Task.CompletedTask; diff --git a/src/EditorFeatures/Test/TextEditor/OpenDocumentTests.cs b/src/EditorFeatures/Test/TextEditor/OpenDocumentTests.cs index 39892260717cd..4651264807e91 100644 --- a/src/EditorFeatures/Test/TextEditor/OpenDocumentTests.cs +++ b/src/EditorFeatures/Test/TextEditor/OpenDocumentTests.cs @@ -48,8 +48,9 @@ public void LinkedFiles() // Confirm the files have been linked by file path. This isn't the core part of this test but without it // nothing else will work. - Assert.Equal(documentIds, workspace.CurrentSolution.GetDocumentIdsWithFilePath(FilePath)); - Assert.Equal(new[] { documentIds.Last() }, workspace.CurrentSolution.GetDocument(documentIds.First()).GetLinkedDocumentIds()); + AssertEx.SetEqual(documentIds, workspace.CurrentSolution.GetDocumentIdsWithFilePath(FilePath)); + Assert.Equal(documentIds.Last(), workspace.CurrentSolution.GetDocument(documentIds.First()).GetLinkedDocumentIds().Single()); + Assert.Equal(documentIds.First(), workspace.CurrentSolution.GetDocument(documentIds.Last()).GetLinkedDocumentIds().Single()); // Now the core test: first, if we make a modified version of the source text, and attempt to get the document for it, // both copies should be updated. diff --git a/src/EditorFeatures/Test/TextEditor/TextBufferAssociatedViewServiceTests.cs b/src/EditorFeatures/Test/TextEditor/TextBufferAssociatedViewServiceTests.cs index bd41099c01fe7..1afa93921c0b1 100644 --- a/src/EditorFeatures/Test/TextEditor/TextBufferAssociatedViewServiceTests.cs +++ b/src/EditorFeatures/Test/TextEditor/TextBufferAssociatedViewServiceTests.cs @@ -31,7 +31,7 @@ public void SanityCheck() var bufferMock = new Mock(MockBehavior.Strict); bufferMock.Setup(b => b.ContentType).Returns(contentType.Object); - var bufferCollection = new Collection(SpecializedCollections.SingletonEnumerable(bufferMock.Object).ToList()); + var bufferCollection = new Collection([bufferMock.Object]); var dummyReason = ConnectionReason.BufferGraphChange; var exportProvider = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider(); diff --git a/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs b/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs index 09da0aa6cb580..c364f5a0ffd40 100644 --- a/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs +++ b/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs @@ -35,7 +35,7 @@ public void Dispose() lock (_tasks) { _tokenSource.Cancel(); - tasks = _tasks.ToArray(); + tasks = [.. _tasks]; } try diff --git a/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs b/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs index d8fd33a73fc2c..33bff5542e174 100644 --- a/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs +++ b/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs @@ -41,7 +41,8 @@ private static string GenerateString(int value) return builder.ToString(); } - private static void Test(bool isCaseSensitive) + [Theory, CombinatorialData] + public void Test(bool isCaseSensitive) { var comparer = isCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; var strings = new HashSet(GenerateStrings(2000).Skip(500).Take(1000), comparer); @@ -79,14 +80,6 @@ private static void Test(bool isCaseSensitive) } } - [Fact] - public void Test1() - => Test(isCaseSensitive: true); - - [Fact] - public void TestInsensitive() - => Test(isCaseSensitive: false); - [Fact] public void TestEmpty() { @@ -106,6 +99,33 @@ public void TestEmpty() } } + [Fact] + public void TestCacheWhenEmpty() + { + BloomFilter.BloomFilterHash.ResetCachedEntry(); + + _ = new BloomFilter(falsePositiveProbability: 0.0001, isCaseSensitive: false, []); + + Assert.False(BloomFilter.BloomFilterHash.TryGetCachedEntry(out _, out _)); + } + + [Fact] + public void TestCacheAfterCalls() + { + var filter1 = new BloomFilter(falsePositiveProbability: 0.0001, isCaseSensitive: false, []); + var filter2 = new BloomFilter(falsePositiveProbability: 0.0001, isCaseSensitive: true, []); + + _ = filter1.ProbablyContains("test1"); + Assert.True(BloomFilter.BloomFilterHash.TryGetCachedEntry(out var isCaseSensitive, out var value)); + Assert.True(!isCaseSensitive); + Assert.Equal("test1", value); + + _ = filter2.ProbablyContains("test2"); + Assert.True(BloomFilter.BloomFilterHash.TryGetCachedEntry(out isCaseSensitive, out value)); + Assert.True(isCaseSensitive); + Assert.Equal("test2", value); + } + [Fact] public void TestSerialization() { @@ -180,6 +200,78 @@ public void TestInt64() } } + [Theory, CombinatorialData] + public void TestCacheCorrectness(bool isCaseSensitive, bool reverse) + { + var allStringsToTest = GenerateStrings(100_000); + + var comparer = isCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + var allHashSets = new List> + { + new HashSet(GenerateStrings(1_000), comparer), + new HashSet(GenerateStrings(1_000).Where((s, i) => i % 1 == 0), comparer), + new HashSet(GenerateStrings(1_000).Where((s, i) => i % 1 == 1), comparer), + new HashSet(GenerateStrings(10_000), comparer), + new HashSet(GenerateStrings(10_000).Where((s, i) => i % 1 == 0), comparer), + new HashSet(GenerateStrings(10_000).Where((s, i) => i % 1 == 1), comparer), + new HashSet(GenerateStrings(100_000), comparer), + new HashSet(GenerateStrings(100_000).Where((s, i) => i % 1 == 0), comparer), + new HashSet(GenerateStrings(100_000).Where((s, i) => i % 1 == 1), comparer), + }; + + // Try the patterns where we're searching smaller filters then larger ones. Then the pattern of larger ones then smaller ones. + if (reverse) + allHashSets.Reverse(); + + // Try several different probability levels to ensure we maintain the correct false positive rate. We + // must always preserve the true 0 negative rate. + for (var d = 0.1; d >= 0.0001; d /= 10) + { + // Get a bloom filter for each set of strings. + var allFilters = allHashSets.Select(s => new BloomFilter(d, isCaseSensitive, s)).ToArray(); + + // The double array stores the correct/incorrect count per run. + var allCounts = allHashSets.Select(_ => new double[2]).ToArray(); + + // We want to take each string, and test it against each bloom filter. This will ensure that the caches + // we have when computing against one bloom filter don't infect the results of the other bloom filters. + foreach (var test in allStringsToTest) + { + for (var i = 0; i < allHashSets.Count; i++) + { + var strings = allHashSets[i]; + var filter = allFilters[i]; + var counts = allCounts[i]; + var actualContains = strings.Contains(test); + var filterContains = filter.ProbablyContains(test); + + // if the filter says no, then it can't be in the real set. + if (!filterContains) + Assert.False(actualContains); + + if (actualContains == filterContains) + { + counts[0]++; + } + else + { + counts[1]++; + } + } + } + + // Now validate for this set of bloom filters, and this particular probability level, that all the + // rates remain correct for each bloom filter. + foreach (var counts in allCounts) + { + var correctCount = counts[0]; + var incorrectCount = counts[1]; + var falsePositivePercentage = incorrectCount / (correctCount + incorrectCount); + Assert.True(falsePositivePercentage < (d * 1.5), string.Format("falsePositivePercentage={0}, d={1}", falsePositivePercentage, d)); + } + } + } + private static HashSet CreateLongs(List ints) { var result = new HashSet(); diff --git a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs index 6ebfbc1b253d4..377d303f6a210 100644 --- a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs @@ -68,7 +68,10 @@ public void TestCreateTextUsesByteOrderMarkIfPresent() TestCreateTextInferredEncoding( textFactoryService, - Encoding.UTF8.GetPreamble().Concat(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetBytes("Test")).ToArray(), + [ + .. Encoding.UTF8.GetPreamble(), + .. new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetBytes("Test"), + ], defaultEncoding: Encoding.GetEncoding(1254), expectedEncoding: Encoding.UTF8); } @@ -82,13 +85,11 @@ public async Task TestCreateFromTemporaryStorage() var text = SourceText.From("Hello, World!"); - // Create a temporary storage location - using var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it - await temporaryStorage.WriteTextAsync(text); + var handle = await temporaryStorageService.WriteToTemporaryStorageAsync(text, CancellationToken.None); // Read text back from it - var text2 = await temporaryStorage.ReadTextAsync(); + var text2 = await handle.ReadFromTemporaryStorageAsync(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); @@ -104,13 +105,11 @@ public async Task TestCreateFromTemporaryStorageWithEncoding() var text = SourceText.From("Hello, World!", Encoding.ASCII); - // Create a temporary storage location - using var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it - await temporaryStorage.WriteTextAsync(text); + var handle = await temporaryStorageService.WriteToTemporaryStorageAsync(text, CancellationToken.None); // Read text back from it - var text2 = await temporaryStorage.ReadTextAsync(); + var text2 = await handle.ReadFromTemporaryStorageAsync(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); diff --git a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb index 10c96b8aa8e94..11e8f0f38c88c 100644 --- a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb +++ b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb @@ -9,14 +9,16 @@ Imports System.Reflection Imports System.Threading Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Copilot Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Imports Microsoft.CodeAnalysis.Editor.UnitTests -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.ErrorLogger Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.UnitTests Imports Roslyn.Utilities Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests @@ -25,9 +27,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Public Class CodeFixServiceTests - Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ - .AddParts(GetType(MockDiagnosticUpdateSourceRegistrationService)) + Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures Private ReadOnly _assemblyLoader As IAnalyzerAssemblyLoader = New InMemoryAssemblyLoader() @@ -54,7 +54,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Dim project = workspace.CurrentSolution.Projects(0) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService))) @@ -130,7 +129,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Dim project = workspace.CurrentSolution.Projects(0) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService))) @@ -263,5 +261,104 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests #Enable Warning RS0005 End Function End Class + + + Public Async Function TestCopilotCodeAnalysisServiceWithoutSyntaxTree() As Task + Dim workspaceDefinition = + + + + var x = {}; // e.g., TypeScript code or anything else that doesn't support compilations + + + + + Dim composition = EditorTestCompositions.EditorFeatures.AddParts( + GetType(NoCompilationContentTypeDefinitions), + GetType(NoCompilationContentTypeLanguageService), + GetType(NoCompilationCopilotCodeAnalysisService)) + + Using workspace = EditorTestWorkspace.Create(workspaceDefinition, composition:=composition) + + Dim document = workspace.CurrentSolution.Projects.Single().Documents.Single() + Dim diagnosticsXml = + + + MappedFile=<%= document.Name %> MappedLine="0" MappedColumn="0" + OriginalFile=<%= document.Name %> OriginalLine="0" OriginalColumn="0" + Message=<%= "Test Message" %>/> + + Dim diagnostics = DiagnosticProviderTests.GetExpectedDiagnostics(workspace, diagnosticsXml) + + Dim copilotCodeAnalysisService = document.Project.Services.GetService(Of ICopilotCodeAnalysisService)() + Dim noCompilationCopilotCodeAnalysisService = DirectCast(copilotCodeAnalysisService, NoCompilationCopilotCodeAnalysisService) + + NoCompilationCopilotCodeAnalysisService.Diagnostics = diagnostics.SelectAsArray(Of Diagnostic)( + Function(d) d.ToDiagnosticAsync(document.Project, CancellationToken.None).Result) + Dim codefixService = workspace.ExportProvider.GetExportedValue(Of ICodeFixService) + + ' Make sure we don't crash + Dim unused = Await codefixService.GetMostSevereFixAsync( + document, Text.TextSpan.FromBounds(0, 0), New DefaultCodeActionRequestPriorityProvider(), CodeActionOptions.DefaultProvider, CancellationToken.None) + End Using + End Function + + + Private Class NoCompilationCopilotOptionsService + Implements ICopilotOptionsService + + + + Public Sub New() + End Sub + + Public Function IsRefineOptionEnabledAsync() As Task(Of Boolean) Implements ICopilotOptionsService.IsRefineOptionEnabledAsync + Return Task.FromResult(True) + End Function + + Public Function IsCodeAnalysisOptionEnabledAsync() As Task(Of Boolean) Implements ICopilotOptionsService.IsCodeAnalysisOptionEnabledAsync + Return Task.FromResult(True) + End Function + + Public Function IsOnTheFlyDocsOptionEnabledAsync() As Task(Of Boolean) Implements ICopilotOptionsService.IsOnTheFlyDocsOptionEnabledASync + Return Task.FromResult(True) + End Function + End Class + + + Private Class NoCompilationCopilotCodeAnalysisService + Implements ICopilotCodeAnalysisService + + + + Public Sub New() + End Sub + + Public Shared Property Diagnostics As ImmutableArray(Of Diagnostic) = ImmutableArray(Of Diagnostic).Empty + + Public Function IsAvailableAsync(cancellationToken As CancellationToken) As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsAvailableAsync + Return Task.FromResult(True) + End Function + + Public Function GetAvailablePromptTitlesAsync(document As Document, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of String)) Implements ICopilotCodeAnalysisService.GetAvailablePromptTitlesAsync + Return Task.FromResult(ImmutableArray.Create("Title")) + End Function + + Public Function AnalyzeDocumentAsync(document As Document, span As TextSpan?, promptTitle As String, cancellationToken As CancellationToken) As Task Implements ICopilotCodeAnalysisService.AnalyzeDocumentAsync + Return Task.CompletedTask + End Function + + Public Function GetCachedDocumentDiagnosticsAsync(document As Document, span As TextSpan?, promptTitles As ImmutableArray(Of String), cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of Diagnostic)) Implements ICopilotCodeAnalysisService.GetCachedDocumentDiagnosticsAsync + Return Task.FromResult(Diagnostics) + End Function + + Public Function StartRefinementSessionAsync(oldDocument As Document, newDocument As Document, primaryDiagnostic As Diagnostic, cancellationToken As CancellationToken) As Task Implements ICopilotCodeAnalysisService.StartRefinementSessionAsync + Return Task.CompletedTask + End Function + + Public Function GetOnTheFlyDocsAsync(symbolSignature As String, declarationCode As ImmutableArray(Of String), language As String, cancellationToken As CancellationToken) As Task(Of String) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsAsync + Return Task.FromResult("") + End Function + End Class End Class End Namespace diff --git a/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb b/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb index f49e402cad388..1eb3762d50b76 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AbstractCrossLanguageUserDiagnosticTest.vb @@ -28,8 +28,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics Protected Const DestinationDocument = "DestinationDocument" Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ - .AddParts(GetType(MockDiagnosticUpdateSourceRegistrationService), GetType(WorkspaceTestLogger)) + .AddParts(GetType(WorkspaceTestLogger)) Private Shared ReadOnly s_composition As TestComposition = s_compositionWithMockDiagnosticUpdateSourceRegistrationService _ .AddParts(GetType(TestAddMetadataReferenceCodeActionOperationFactoryWorkspaceService)) diff --git a/src/EditorFeatures/Test2/Diagnostics/AdditionalFileDiagnosticsTests.vb b/src/EditorFeatures/Test2/Diagnostics/AdditionalFileDiagnosticsTests.vb index da1d0c1a02234..1f0349b7979f4 100644 --- a/src/EditorFeatures/Test2/Diagnostics/AdditionalFileDiagnosticsTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/AdditionalFileDiagnosticsTests.vb @@ -16,9 +16,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.AdditionalFiles Public Class AdditionalFileDiagnosticsTests Inherits AbstractCrossLanguageUserDiagnosticTest - Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ - .AddParts(GetType(MockDiagnosticUpdateSourceRegistrationService)) + Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace, language As String) As (DiagnosticAnalyzer, CodeFixProvider) Return (New AdditionalFileAnalyzer(), New AdditionalFileFixer()) diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb index 4768b8eab7245..19a9f764f34be 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb @@ -7,9 +7,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.CSharp Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Editor.UnitTests -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces -Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.SolutionCrawler Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.UnitTests @@ -37,11 +35,9 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Private Const s_mappedFileAttributeName As String = "MappedFile" Private Shared ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeatures _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ .AddParts( GetType(NoCompilationContentTypeLanguageService), - GetType(NoCompilationContentTypeDefinitions), - GetType(MockDiagnosticUpdateSourceRegistrationService)) + GetType(NoCompilationContentTypeDefinitions)) Public Sub TestNoErrors() @@ -259,21 +255,15 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Private Shared Sub VerifyAllAvailableDiagnostics(test As XElement, diagnostics As XElement, Optional ordered As Boolean = True) Using workspace = EditorTestWorkspace.CreateWorkspace(test, composition:=s_composition) - - Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( - workspace.CurrentSolution.Options.WithChangedOption(New OptionKey(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag), False)))) - ' Ensure that diagnostic service computes diagnostics for all open files, not just the active file (default mode) For Each language In workspace.Projects.Select(Function(p) p.Language).Distinct() workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, language, BackgroundAnalysisScope.OpenFiles) Next Dim diagnosticProvider = GetDiagnosticProvider(workspace) - Dim actualDiagnostics = diagnosticProvider.GetDiagnosticsAsync( - workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None).Result + Dim actualDiagnostics = diagnosticProvider.GetDiagnosticsForIdsAsync( + workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None).Result If diagnostics Is Nothing Then Assert.Equal(0, actualDiagnostics.Length) @@ -296,13 +286,12 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference = New TestAnalyzerReferenceByLanguage(compilerAnalyzersMap) workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim analyzerService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Return analyzerService End Function - Private Shared Function GetExpectedDiagnostics(workspace As EditorTestWorkspace, diagnostics As XElement) As List(Of DiagnosticData) + Friend Shared Function GetExpectedDiagnostics(workspace As EditorTestWorkspace, diagnostics As XElement) As List(Of DiagnosticData) Dim result As New List(Of DiagnosticData) Dim mappedLine As Integer, mappedColumn As Integer, originalLine As Integer, originalColumn As Integer Dim Id As String, message As String, originalFile As String, mappedFile As String diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 0427fb528af0a..d6722a2eee3cf 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -28,9 +28,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests <[UseExportProvider]> Public Class DiagnosticServiceTests - Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ - .AddParts(GetType(MockDiagnosticUpdateSourceRegistrationService)) + Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures Private ReadOnly _assemblyLoader As IAnalyzerAssemblyLoader = New InMemoryAssemblyLoader() @@ -92,7 +90,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim hostAnalyzers = solution.SolutionState.Analyzers Dim project = solution.Projects(0) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) ' Verify available diagnostic descriptors/analyzers @@ -198,7 +195,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim project = solution.Projects(0) Dim hostAnalyzers = solution.SolutionState.Analyzers - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) ' Add project analyzer reference with no analyzers. @@ -243,7 +239,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim projectAnalyzerReference = New AnalyzerImageReference( ImmutableArray.Create(Of DiagnosticAnalyzer)(New TestDiagnosticAnalyzer1(1)), display:=referenceName) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) project = project.WithAnalyzerReferences(ImmutableArray.Create(Of AnalyzerReference)(projectAnalyzerReference)) @@ -280,7 +275,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim project = solution.Projects(0) Dim hostAnalyzers = solution.SolutionState.Analyzers - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) ' Verify available diagnostic descriptors/analyzers @@ -351,7 +345,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests solution = p2.Solution Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -394,7 +387,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference = New TestAnalyzerReferenceByLanguage(analyzersMap) workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService2 = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptors = workspace.CurrentSolution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService2.AnalyzerInfoCache) @@ -443,7 +435,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference2 = CreateAnalyzerFileReference(Assembly.GetExecutingAssembly().Location) project = project.AddAnalyzerReference(analyzerReference2) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim descriptorsMap = workspace.CurrentSolution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -498,7 +489,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -534,7 +524,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -547,45 +536,15 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, root.FullSpan) Assert.Equal(0, diagnostics.Count()) - diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, projectId:=Nothing, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None).ConfigureAwait(False) + diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Dim diagnostic = diagnostics.First() Assert.True(diagnostic.Id = "AD0001") Assert.Contains("CodeBlockStartedAnalyzer", diagnostic.Message, StringComparison.Ordinal) End Using End Function - - Public Sub TestDiagnosticAnalyzerExceptionHandledNoCrash() - Dim test = - - - - - - Using workspace = TestWorkspace.CreateWorkspace(test) - Dim project = workspace.CurrentSolution.Projects.Single() - Dim analyzer = New CodeBlockStartedAnalyzer(Of Microsoft.CodeAnalysis.CSharp.SyntaxKind) - - Dim expected = Diagnostic.Create("test", "test", "test", DiagnosticSeverity.Error, DiagnosticSeverity.Error, True, 0) - Dim exceptionDiagnosticsSource = New TestHostDiagnosticUpdateSource(workspace) - - ' check reporting diagnostic to a project that doesn't exist - exceptionDiagnosticsSource.ReportAnalyzerDiagnostic(analyzer, expected, ProjectId.CreateFromSerialized(Guid.NewGuid(), "dummy")) - Dim diagnostics = exceptionDiagnosticsSource.GetTestAccessor().GetReportedDiagnostics(analyzer) - Assert.Equal(0, diagnostics.Count()) - - ' check workspace diagnostic reporting - exceptionDiagnosticsSource.ReportAnalyzerDiagnostic(analyzer, expected, projectId:=Nothing) - diagnostics = exceptionDiagnosticsSource.GetTestAccessor().GetReportedDiagnostics(analyzer) - - Assert.Equal(1, diagnostics.Count()) - Assert.Equal(expected.Id, diagnostics.First().Id) - End Using - End Sub - Public Async Function TestDiagnosticAnalyzer_FileLoadFailure() As Task Dim test = @@ -611,7 +570,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -635,7 +593,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Using workspace = TestWorkspace.CreateWorkspace(test, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -650,10 +607,9 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Assert.Equal(1, descriptorsMap.Count) Dim document = project.Documents.Single() - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, diagnostics.Count()) Dim diagnostic = diagnostics.First() Assert.Equal(OperationAnalyzer.Descriptor.Id, diagnostic.Id) @@ -681,7 +637,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -716,7 +671,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) ' Ensure no duplicate diagnostics. @@ -793,7 +747,6 @@ class AnonymousFunctions project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) ' Ensure no duplicate diagnostics. @@ -830,7 +783,6 @@ class AnonymousFunctions project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -989,7 +941,6 @@ class AnonymousFunctions Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -998,10 +949,9 @@ class AnonymousFunctions ' Verify no duplicate analysis/diagnostics. Dim document = project.Documents.Single() - Dim diagnostics = (Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None)). + Dim diagnostics = (Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None)). Select(Function(d) d.Id = NamedTypeAnalyzer.DiagDescriptor.Id) Assert.Equal(1, diagnostics.Count) @@ -1031,7 +981,6 @@ class AnonymousFunctions project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -1088,7 +1037,6 @@ class AnonymousFunctions project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -1096,10 +1044,9 @@ class AnonymousFunctions ' Verify project diagnostics contains diagnostics reported on both partial definitions. Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(2, diagnostics.Count()) Dim file1HasDiag = False, file2HasDiag = False For Each diagnostic In diagnostics @@ -1142,7 +1089,6 @@ public class B project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -1186,7 +1132,6 @@ public class B project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -1230,7 +1175,6 @@ public class B project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -1283,7 +1227,6 @@ End Class project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -1347,7 +1290,6 @@ public class B project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -1395,7 +1337,6 @@ public class B project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -1467,7 +1408,6 @@ public class B project = additionalDoc.Project Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -2018,7 +1958,6 @@ End Class project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -2079,7 +2018,6 @@ namespace ConsoleApplication1 project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -2147,7 +2085,6 @@ class MyClass project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -2187,7 +2124,6 @@ class MyClass project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -2211,10 +2147,9 @@ class MyClass Assert.Equal(expectedCount, diagnostics.Count()) ' Get diagnostics explicitly - Dim hiddenDiagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim hiddenDiagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, hiddenDiagnostics.Count()) Assert.Equal(analyzer.Descriptor.Id, hiddenDiagnostics.Single().Id) End Using @@ -2252,7 +2187,6 @@ class C Dim span = localDecl.Span Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -2294,17 +2228,15 @@ class C project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(0, diagnostics.Count()) End Using End Function @@ -2345,7 +2277,6 @@ class MyClass Dim span = localDecl.Span Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -2398,7 +2329,6 @@ class MyClass project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -2469,7 +2399,6 @@ class MyClass project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) @@ -2564,7 +2493,6 @@ public class C project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb index ed69904f96831..d04b8b108da6d 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb @@ -712,7 +712,7 @@ class c Public Function InvokeWithTrueKeywordCommitSeeLangword(showCompletionInArgumentLists As Boolean) As Task - Return InvokeWithKeywordCommitSeeLangword("true", showCompletionInArgumentLists) + Return InvokeWithKeywordCommitSeeLangword("true", showCompletionInArgumentLists, unique:=False) End Function @@ -740,7 +740,7 @@ class c Return InvokeWithKeywordCommitSeeLangword("await", showCompletionInArgumentLists) End Function - Private Shared Async Function InvokeWithKeywordCommitSeeLangword(keyword As String, showCompletionInArgumentLists As Boolean) As Task + Private Shared Async Function InvokeWithKeywordCommitSeeLangword(keyword As String, showCompletionInArgumentLists As Boolean, Optional unique As Boolean = True) As Task Using state = TestStateFactory.CreateCSharpTestState( $$ diff --git a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb index ea5b97d706b47..df068451d6875 100644 --- a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb +++ b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb @@ -637,12 +637,12 @@ End Class Public Function InvokeWithTrueKeywordCommitSeeLangword() As Task - Return InvokeWithKeywordCommitSeeLangword("True") + Return InvokeWithKeywordCommitSeeLangword("True", unique:=False) End Function Public Function InvokeWithFalseKeywordCommitSeeLangword() As Task - Return InvokeWithKeywordCommitSeeLangword("False") + Return InvokeWithKeywordCommitSeeLangword("False", unique:=False) End Function diff --git a/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb b/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb index 72aacd6fcf71a..cfb36ab2cfafb 100644 --- a/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb +++ b/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb @@ -40,7 +40,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.KeywordHighlighting visibilityTracker:=Nothing, AsynchronousOperationListenerProvider.NullProvider) - Dim context = New TaggerContext(Of KeywordHighlightTag)(document, snapshot, New SnapshotPoint(snapshot, caretPosition)) + Dim context = New TaggerContext(Of KeywordHighlightTag)(document, snapshot, frozenPartialSemantics:=False, New SnapshotPoint(snapshot, caretPosition)) Await tagProducer.GetTestAccessor().ProduceTagsAsync(context) Dim producedTags = From tag In context.TagSpans diff --git a/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb b/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb index 86e949be586ea..e8777830bcc54 100644 --- a/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb +++ b/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb @@ -42,7 +42,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.ReferenceHighlighting Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id) Dim context = New TaggerContext(Of NavigableHighlightTag)( - document, snapshot, New SnapshotPoint(snapshot, caretPosition)) + document, snapshot, frozenPartialSemantics:=False, New SnapshotPoint(snapshot, caretPosition)) Await tagProducer.GetTestAccessor().ProduceTagsAsync(context) Dim producedTags = From tag In context.TagSpans diff --git a/src/EditorFeatures/TestUtilities/BraceHighlighting/AbstractBraceHighlightingTests.cs b/src/EditorFeatures/TestUtilities/BraceHighlighting/AbstractBraceHighlightingTests.cs index 198342c5d430f..fd0d09b17e18f 100644 --- a/src/EditorFeatures/TestUtilities/BraceHighlighting/AbstractBraceHighlightingTests.cs +++ b/src/EditorFeatures/TestUtilities/BraceHighlighting/AbstractBraceHighlightingTests.cs @@ -51,7 +51,7 @@ protected async Task TestBraceHighlightingAsync( var buffer = testDocument.GetTextBuffer(); var document = buffer.CurrentSnapshot.GetRelatedDocumentsWithChanges().FirstOrDefault(); var context = new TaggerContext( - document, buffer.CurrentSnapshot, + document, buffer.CurrentSnapshot, frozenPartialSemantics: false, new SnapshotPoint(buffer.CurrentSnapshot, cursorPosition)); await provider.GetTestAccessor().ProduceTagsAsync(context); diff --git a/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs b/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs index b962707454438..717b35ad533ba 100644 --- a/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs +++ b/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs @@ -90,6 +90,10 @@ public static FormattedClassification Property(string text) public static FormattedClassification Event(string text) => New(text, ClassificationTypeNames.EventName); + [DebuggerStepThrough] + public static FormattedClassification Obsolete(string text) + => New(text, ClassificationTypeNames.ObsoleteSymbol); + [DebuggerStepThrough] public static FormattedClassification Static(string text) => New(text, ClassificationTypeNames.StaticSymbol); diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/DiagnosticTaggerWrapper.cs b/src/EditorFeatures/TestUtilities/Diagnostics/DiagnosticTaggerWrapper.cs index 40c80229ce774..b3fd01ef67bb0 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/DiagnosticTaggerWrapper.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/DiagnosticTaggerWrapper.cs @@ -24,7 +24,6 @@ internal class DiagnosticTaggerWrapper where TTag : ITag { private readonly EditorTestWorkspace _workspace; - public readonly DiagnosticService DiagnosticService; private readonly IThreadingContext _threadingContext; private readonly IAsynchronousOperationListenerProvider _listenerProvider; @@ -33,7 +32,6 @@ internal class DiagnosticTaggerWrapper public DiagnosticTaggerWrapper( EditorTestWorkspace workspace, IReadOnlyDictionary>? analyzerMap = null, - IDiagnosticUpdateSource? updateSource = null, bool createTaggerProvider = true) { _threadingContext = workspace.GetService(); @@ -49,13 +47,6 @@ public DiagnosticTaggerWrapper( _workspace = workspace; - DiagnosticService = (DiagnosticService)workspace.ExportProvider.GetExportedValue(); - - if (updateSource is object) - { - DiagnosticService.Register(updateSource); - } - if (createTaggerProvider) { _ = TaggerProvider; diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs index cf0f9c741a49f..424a6876218c1 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs @@ -22,7 +22,7 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics internal class MockDiagnosticAnalyzerService : IDiagnosticAnalyzerService { private readonly ArrayBuilder<(DiagnosticData Diagnostic, DiagnosticKind KindFilter)> _diagnosticsWithKindFilter; - public readonly List DocumentsToReanalyze = []; + public bool RequestedRefresh; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -41,8 +41,8 @@ public void AddDiagnostics(ImmutableArray diagnostics, Diagnosti AddDiagnostic(diagnostic, diagnosticKind); } - public void Reanalyze(Workspace workspace, IEnumerable? projectIds, IEnumerable? documentIds, bool highPriority) - => DocumentsToReanalyze.AddRange(documentIds); + public void RequestDiagnosticRefresh() + => RequestedRefresh = true; public DiagnosticAnalyzerInfoCache AnalyzerInfoCache => throw new NotImplementedException(); @@ -61,7 +61,7 @@ public Task> GetCachedDiagnosticsAsync(Workspace public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => throw new NotImplementedException(); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task> GetDiagnosticsForSpanAsync(TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeCompilerDiagnostics, bool includeSuppressedDiagnostics, ICodeActionRequestPriorityProvider priorityProvider, Func? addOperationScope, DiagnosticKind diagnosticKind, bool isExplicit, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/TestAnalyzerReferenceByLanguage.cs b/src/EditorFeatures/TestUtilities/Diagnostics/TestAnalyzerReferenceByLanguage.cs index 7054ecba82d37..b8386043366ce 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/TestAnalyzerReferenceByLanguage.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/TestAnalyzerReferenceByLanguage.cs @@ -39,7 +39,7 @@ public override ImmutableArray GetAnalyzers(string language) public TestAnalyzerReferenceByLanguage WithAdditionalAnalyzers(string language, IEnumerable analyzers) { var newAnalyzersMap = ImmutableDictionary.CreateRange( - _analyzersMap.Select(kvp => new KeyValuePair>( + _analyzersMap.Select(kvp => KeyValuePairUtil.Create( kvp.Key, kvp.Key == language ? kvp.Value.AddRange(analyzers) : kvp.Value))); return new(newAnalyzersMap); } diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/TestHostDiagnosticUpdateSource.cs b/src/EditorFeatures/TestUtilities/Diagnostics/TestHostDiagnosticUpdateSource.cs deleted file mode 100644 index 12d2dfaa53ca5..0000000000000 --- a/src/EditorFeatures/TestUtilities/Diagnostics/TestHostDiagnosticUpdateSource.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 Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics -{ - internal class TestHostDiagnosticUpdateSource : AbstractHostDiagnosticUpdateSource - { - private readonly Workspace _workspace; - - public TestHostDiagnosticUpdateSource(Workspace workspace) - => _workspace = workspace; - - public override Workspace Workspace - { - get - { - return _workspace; - } - } - - public override int GetHashCode() - => _workspace.GetHashCode(); - } -} diff --git a/src/EditorFeatures/TestUtilities/DocumentTracking/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/DocumentTracking/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs new file mode 100644 index 0000000000000..4f8ccd9d7cdc6 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/DocumentTracking/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.Test; + +internal sealed class FirstDocumentIsActiveAndVisibleDocumentTrackingService : IDocumentTrackingService +{ + private readonly Workspace _workspace; + + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + private FirstDocumentIsActiveAndVisibleDocumentTrackingService(Workspace workspace) + => _workspace = workspace; + + public event EventHandler ActiveDocumentChanged { add { } remove { } } + + public DocumentId TryGetActiveDocument() + => _workspace.CurrentSolution.Projects.First().DocumentIds.First(); + + public ImmutableArray GetVisibleDocuments() + => [TryGetActiveDocument()]; + + [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 FirstDocumentIsActiveAndVisibleDocumentTrackingService(workspaceServices.Workspace); + } +} diff --git a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs new file mode 100644 index 0000000000000..ec01c8c69affc --- /dev/null +++ b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Editor.Test; + +[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class TestDocumentTrackingService() : IDocumentTrackingService +{ + private DocumentId? _activeDocumentId; + + public event EventHandler? ActiveDocumentChanged; + + public void SetActiveDocument(DocumentId? newActiveDocumentId) + { + _activeDocumentId = newActiveDocumentId; + ActiveDocumentChanged?.Invoke(this, newActiveDocumentId); + } + + public DocumentId? TryGetActiveDocument() + => _activeDocumentId; + + public ImmutableArray GetVisibleDocuments() + => _activeDocumentId != null ? [_activeDocumentId] : []; +} diff --git a/src/EditorFeatures/TestUtilities/GoToAdjacentMember/AbstractGoToAdjacentMemberTests.cs b/src/EditorFeatures/TestUtilities/GoToAdjacentMember/AbstractGoToAdjacentMemberTests.cs index 8184100af8e92..60af72e28cb9f 100644 --- a/src/EditorFeatures/TestUtilities/GoToAdjacentMember/AbstractGoToAdjacentMemberTests.cs +++ b/src/EditorFeatures/TestUtilities/GoToAdjacentMember/AbstractGoToAdjacentMemberTests.cs @@ -26,7 +26,7 @@ protected async Task AssertNavigatedAsync(string code, bool next, SourceCodeKind { var kinds = sourceCodeKind != null ? SpecializedCollections.SingletonEnumerable(sourceCodeKind.Value) - : new[] { SourceCodeKind.Regular, SourceCodeKind.Script }; + : [SourceCodeKind.Regular, SourceCodeKind.Script]; foreach (var kind in kinds) { diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index df7b41725e8af..b4b32bd2383e1 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -301,21 +301,21 @@ private protected static CodeActionResolveData CreateCodeActionResolveData(strin private protected Task CreateTestLspServerAsync(string markup, bool mutatingLspWorkspace, LSP.ClientCapabilities clientCapabilities, bool callInitialized = true) => CreateTestLspServerAsync([markup], LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = clientCapabilities, CallInitialized = callInitialized }); - private protected Task CreateTestLspServerAsync(string markup, bool mutatingLspWorkspace, InitializationOptions? initializationOptions = null, List? extraExportedTypes = null) - => CreateTestLspServerAsync([markup], LanguageNames.CSharp, mutatingLspWorkspace, initializationOptions, extraExportedTypes: extraExportedTypes); + private protected Task CreateTestLspServerAsync(string markup, bool mutatingLspWorkspace, InitializationOptions? initializationOptions = null, TestComposition? composition = null) + => CreateTestLspServerAsync([markup], LanguageNames.CSharp, mutatingLspWorkspace, initializationOptions, composition); - private protected Task CreateTestLspServerAsync(string[] markups, bool mutatingLspWorkspace, InitializationOptions? initializationOptions = null, List? extraExportedTypes = null) - => CreateTestLspServerAsync(markups, LanguageNames.CSharp, mutatingLspWorkspace, initializationOptions, extraExportedTypes: extraExportedTypes); + private protected Task CreateTestLspServerAsync(string[] markups, bool mutatingLspWorkspace, InitializationOptions? initializationOptions = null, TestComposition? composition = null) + => CreateTestLspServerAsync(markups, LanguageNames.CSharp, mutatingLspWorkspace, initializationOptions, composition); - private protected Task CreateVisualBasicTestLspServerAsync(string markup, bool mutatingLspWorkspace, InitializationOptions? initializationOptions = null, List? extraExportedTypes = null) - => CreateTestLspServerAsync([markup], LanguageNames.VisualBasic, mutatingLspWorkspace, initializationOptions, extraExportedTypes: extraExportedTypes); + private protected Task CreateVisualBasicTestLspServerAsync(string markup, bool mutatingLspWorkspace, InitializationOptions? initializationOptions = null, TestComposition? composition = null) + => CreateTestLspServerAsync([markup], LanguageNames.VisualBasic, mutatingLspWorkspace, initializationOptions, composition); private protected Task CreateTestLspServerAsync( - string[] markups, string languageName, bool mutatingLspWorkspace, InitializationOptions? initializationOptions, List? excludedTypes = null, List? extraExportedTypes = null, bool commonReferences = true) + string[] markups, string languageName, bool mutatingLspWorkspace, InitializationOptions? initializationOptions, TestComposition? composition = null, bool commonReferences = true) { var lspOptions = initializationOptions ?? new InitializationOptions(); - var workspace = CreateWorkspace(lspOptions, workspaceKind: null, mutatingLspWorkspace, excludedTypes, extraExportedTypes); + var workspace = CreateWorkspace(lspOptions, workspaceKind: null, mutatingLspWorkspace, composition); workspace.InitializeDocuments( TestWorkspace.CreateWorkspaceElement(languageName, files: markups, fileContainingFolders: lspOptions.DocumentFileContainingFolders, sourceGeneratedFiles: lspOptions.SourceGeneratedMarkups, commonReferences: commonReferences), @@ -381,18 +381,10 @@ private protected async Task CreateXmlTestLspServerAsync( } internal EditorTestWorkspace CreateWorkspace( - InitializationOptions? options, string? workspaceKind, bool mutatingLspWorkspace, List? excludedTypes = null, List? extraExportedTypes = null) + InitializationOptions? options, string? workspaceKind, bool mutatingLspWorkspace, TestComposition? composition = null) { - var composition = Composition; - - if (excludedTypes is not null) - composition = composition.AddExcludedPartTypes(excludedTypes); - - if (extraExportedTypes is not null) - composition = composition.AddParts(extraExportedTypes); - var workspace = new EditorTestWorkspace( - composition, workspaceKind, configurationOptions: new WorkspaceConfigurationOptions(EnableOpeningSourceGeneratedFiles: true), supportsLspMutation: mutatingLspWorkspace); + composition ?? Composition, workspaceKind, configurationOptions: new WorkspaceConfigurationOptions(EnableOpeningSourceGeneratedFiles: true), supportsLspMutation: mutatingLspWorkspace); options?.OptionUpdater?.Invoke(workspace.GetService()); workspace.GetService().Register(workspace); @@ -520,6 +512,13 @@ private static LSP.DidCloseTextDocumentParams CreateDidCloseTextDocumentParams(U } }; + internal static JsonMessageFormatter CreateJsonMessageFormatter() + { + var messageFormatter = new JsonMessageFormatter(); + LSP.VSInternalExtensionUtilities.AddVSInternalExtensionConverters(messageFormatter.JsonSerializer); + return messageFormatter; + } + internal sealed class TestLspServer : IAsyncDisposable { public readonly EditorTestWorkspace TestWorkspace; @@ -565,13 +564,6 @@ private void InitializeClientRpc() Assert.False(workspaceWaiter.HasPendingWork); } - private static JsonMessageFormatter CreateJsonMessageFormatter() - { - var messageFormatter = new JsonMessageFormatter(); - LSP.VSInternalExtensionUtilities.AddVSInternalExtensionConverters(messageFormatter.JsonSerializer); - return messageFormatter; - } - internal static async Task CreateAsync(EditorTestWorkspace testWorkspace, InitializationOptions initializationOptions, AbstractLspLogger logger) { var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); @@ -613,20 +605,15 @@ internal static async Task CreateAsync(EditorTestWorkspace testWo private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, EditorTestWorkspace workspace, WellKnownLspServerKinds serverKind, AbstractLspLogger logger) { var capabilitiesProvider = workspace.ExportProvider.GetExportedValue(); - var servicesProvider = workspace.ExportProvider.GetExportedValue(); + var factory = workspace.ExportProvider.GetExportedValue(); - var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, CreateJsonMessageFormatter())) + var jsonMessageFormatter = CreateJsonMessageFormatter(); + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, jsonMessageFormatter)) { ExceptionStrategy = ExceptionProcessing.ISerializable, }; - var languageServer = new RoslynLanguageServer( - servicesProvider, jsonRpc, - capabilitiesProvider, - logger, - workspace.Services.HostServices, - ProtocolConstants.RoslynLspLanguages, - serverKind); + var languageServer = (RoslynLanguageServer)factory.Create(jsonRpc, jsonMessageFormatter.JsonSerializer, capabilitiesProvider, serverKind, logger, workspace.Services.HostServices); jsonRpc.StartListening(); return languageServer; @@ -646,6 +633,16 @@ private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Str return result; } + public Task ExecuteNotificationAsync(string methodName, RequestType request) where RequestType : class + { + return _clientRpc.NotifyWithParameterObjectAsync(methodName, request); + } + + public Task ExecuteNotification0Async(string methodName) + { + return _clientRpc.NotifyWithParameterObjectAsync(methodName); + } + public async Task OpenDocumentAsync(Uri documentUri, string? text = null, string languageId = "") { if (text == null) @@ -710,6 +707,14 @@ public async Task ExitTestServerAsync() public Solution GetCurrentSolution() => TestWorkspace.CurrentSolution; + public async Task AssertServerShuttingDownAsync() + { + var queueAccessor = GetQueueAccessor()!.Value; + await queueAccessor.WaitForProcessingToStopAsync().ConfigureAwait(false); + Assert.True(GetServerAccessor().HasShutdownStarted()); + Assert.True(queueAccessor.IsComplete()); + } + internal async Task WaitForDiagnosticsAsync() { var listenerProvider = TestWorkspace.GetService(); diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index 5685f62e13433..22b76a49786d6 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Editor.Implementation.NavigateTo; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; @@ -37,11 +38,11 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigateTo { [UseExportProvider] - public abstract class AbstractNavigateToTests + public abstract partial class AbstractNavigateToTests { protected static readonly TestComposition DefaultComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService)); protected static readonly TestComposition FirstVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocIsVisibleDocumentTrackingService.Factory)); - protected static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocIsActiveAndVisibleDocumentTrackingService.Factory)); + protected static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); protected INavigateToItemProvider _provider; protected NavigateToTestAggregator _aggregator; @@ -162,7 +163,7 @@ internal void InitializeWorkspace(EditorTestWorkspace workspace) protected static void VerifyNavigateToResultItems( List expecteditems, IEnumerable items) { - expecteditems = expecteditems.OrderBy(i => i.Name).ToList(); + expecteditems = [.. expecteditems.OrderBy(i => i.Name)]; items = items.OrderBy(i => i.Name).ToList(); Assert.Equal(expecteditems.Count(), items.Count()); @@ -237,10 +238,7 @@ private class FirstDocIsVisibleDocumentTrackingService : IDocumentTrackingServic private FirstDocIsVisibleDocumentTrackingService(Workspace workspace) => _workspace = workspace; - public bool SupportsDocumentTracking => true; - public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } public DocumentId TryGetActiveDocument() => null; @@ -263,40 +261,6 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) } } - private class FirstDocIsActiveAndVisibleDocumentTrackingService : IDocumentTrackingService - { - private readonly 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 { } } - - public DocumentId TryGetActiveDocument() - => _workspace.CurrentSolution.Projects.First().DocumentIds.First(); - - 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 FirstDocIsActiveAndVisibleDocumentTrackingService(workspaceServices.Workspace); - } - } - [ExportWorkspaceService(typeof(IWorkspaceNavigateToSearcherHostService))] [PartNotDiscoverable, Shared] [method: ImportingConstructor] diff --git a/src/EditorFeatures/TestUtilities/ObsoleteSymbol/AbstractObsoleteSymbolTests.cs b/src/EditorFeatures/TestUtilities/ObsoleteSymbol/AbstractObsoleteSymbolTests.cs new file mode 100644 index 0000000000000..05057c372ad75 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/ObsoleteSymbol/AbstractObsoleteSymbolTests.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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ObsoleteSymbol; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.ObsoleteSymbol; + +[UseExportProvider] +public abstract class AbstractObsoleteSymbolTests +{ + protected abstract EditorTestWorkspace 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 documents = project.Documents.ToImmutableArray(); + + for (var i = 0; i < documents.Length; i++) + { + var document = documents[i]; + var text = await document.GetTextAsync(); + + var service = document.GetRequiredLanguageService(); + var textSpans = ImmutableArray.Create(new TextSpan(0, text.Length)); + var result = await service.GetLocationsAsync(document, textSpans, CancellationToken.None); + + var expectedSpans = workspace.Documents[i].SelectedSpans.OrderBy(s => s.Start); + var actualSpans = result.OrderBy(s => s.Start); + + AssertEx.EqualOrDiff( + string.Join(Environment.NewLine, expectedSpans), + string.Join(Environment.NewLine, actualSpans)); + } + } +} diff --git a/src/EditorFeatures/TestUtilities/Preview/MockPreviewPaneService.cs b/src/EditorFeatures/TestUtilities/Preview/MockPreviewPaneService.cs index 3fea5e6d0da58..d7941be6d254d 100644 --- a/src/EditorFeatures/TestUtilities/Preview/MockPreviewPaneService.cs +++ b/src/EditorFeatures/TestUtilities/Preview/MockPreviewPaneService.cs @@ -27,7 +27,7 @@ public MockPreviewPaneService() public object GetPreviewPane(DiagnosticData diagnostic, IReadOnlyList previewContents) { - var contents = previewContents ?? SpecializedCollections.EmptyEnumerable(); + var contents = previewContents ?? []; foreach (var content in contents.OfType()) { diff --git a/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs deleted file mode 100644 index 91f7f5d0df0ec..0000000000000 --- a/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.Editor.Test -{ - [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal sealed class TestDocumentTrackingService : IDocumentTrackingService - { - private DocumentId? _activeDocumentId; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestDocumentTrackingService() - { - } - - public bool SupportsDocumentTracking => true; - - public event EventHandler? ActiveDocumentChanged; - - public event EventHandler NonRoslynBufferTextChanged - { - add { } - remove { } - } - - public void SetActiveDocument(DocumentId? newActiveDocumentId) - { - _activeDocumentId = newActiveDocumentId; - ActiveDocumentChanged?.Invoke(this, newActiveDocumentId); - } - - public DocumentId? TryGetActiveDocument() - => _activeDocumentId; - - public ImmutableArray GetVisibleDocuments() - => _activeDocumentId != null ? ImmutableArray.Create(_activeDocumentId) : ImmutableArray.Empty; - } -} diff --git a/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs b/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs index 1a76d41a4f352..90ed3e6ea22e0 100644 --- a/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs +++ b/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs @@ -28,7 +28,7 @@ public static class SquiggleUtilities internal static TestComposition WpfCompositionWithSolutionCrawler = EditorTestCompositions.EditorFeaturesWpf .RemoveParts(typeof(MockWorkspaceEventListenerProvider)); - internal static async Task<(ImmutableArray, ImmutableArray>)> GetDiagnosticsAndErrorSpansAsync( + internal static async Task>> GetTagSpansAsync( EditorTestWorkspace workspace, IReadOnlyDictionary> analyzerMap = null) where TProvider : AbstractDiagnosticsTaggerProvider @@ -43,14 +43,10 @@ public static class SquiggleUtilities using var disposable = tagger as IDisposable; await wrapper.WaitForTags(); - var service = (DiagnosticAnalyzerService)workspace.ExportProvider.GetExportedValue(); - var analyzerDiagnostics = await service.GetDiagnosticsAsync(workspace.CurrentSolution, - projectId: null, documentId: null, includeSuppressedDiagnostics: false, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); - var snapshot = textBuffer.CurrentSnapshot; var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToImmutableArray(); - return (analyzerDiagnostics, spans); + return spans; } } } diff --git a/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs b/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs index b18dd1053b470..d42a8b5abcc4d 100644 --- a/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs +++ b/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs @@ -5,14 +5,10 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text.Tagging; using Roslyn.Utilities; @@ -22,34 +18,11 @@ internal sealed class TestDiagnosticTagProducer where TProvider : AbstractDiagnosticsTaggerProvider where TTag : class, ITag { - internal static Task<(ImmutableArray, ImmutableArray>)> GetDiagnosticsAndErrorSpans( + internal static Task>> GetTagSpansAsync( EditorTestWorkspace workspace, IReadOnlyDictionary>? analyzerMap = null) { - return SquiggleUtilities.GetDiagnosticsAndErrorSpansAsync(workspace, analyzerMap); - } - - internal static async Task>> GetErrorsFromUpdateSource(EditorTestWorkspace workspace, DiagnosticsUpdatedArgs updateArgs, DiagnosticKind diagnosticKind) - { - var source = new TestDiagnosticUpdateSource(); - - var wrapper = new DiagnosticTaggerWrapper(workspace, updateSource: source); - - var firstDocument = workspace.Documents.First(); - var tagger = wrapper.TaggerProvider.CreateTagger(firstDocument.GetTextBuffer()); - using var disposable = (IDisposable)tagger; - - var analyzerServer = (MockDiagnosticAnalyzerService)workspace.GetService(); - analyzerServer.AddDiagnostics(updateArgs.Diagnostics, diagnosticKind); - - source.RaiseDiagnosticsUpdated(ImmutableArray.Create(updateArgs)); - - await wrapper.WaitForTags(); - - var snapshot = firstDocument.GetTextBuffer().CurrentSnapshot; - var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToImmutableArray(); - - return spans; + return SquiggleUtilities.GetTagSpansAsync(workspace, analyzerMap); } internal static DiagnosticData CreateDiagnosticData(EditorTestHostDocument document, TextSpan span) @@ -72,15 +45,4 @@ internal static DiagnosticData CreateDiagnosticData(EditorTestHostDocument docum location: new DiagnosticDataLocation(new FileLinePositionSpan(document.FilePath, linePosSpan), document.Id), language: document.Project.Language); } - - private class TestDiagnosticUpdateSource : IDiagnosticUpdateSource - { - public void RaiseDiagnosticsUpdated(ImmutableArray args) - { - DiagnosticsUpdated?.Invoke(this, args); - } - - public event EventHandler>? DiagnosticsUpdated; - public event EventHandler DiagnosticsCleared { add { } remove { } } - } } diff --git a/src/EditorFeatures/TestUtilities/Threading/WpfFactDiscoverer.cs b/src/EditorFeatures/TestUtilities/Threading/WpfFactDiscoverer.cs index dedb5480df120..e5498a5402cba 100644 --- a/src/EditorFeatures/TestUtilities/Threading/WpfFactDiscoverer.cs +++ b/src/EditorFeatures/TestUtilities/Threading/WpfFactDiscoverer.cs @@ -32,13 +32,13 @@ public WpfTheoryDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnostic protected override IEnumerable CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) { var testCase = new WpfTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, dataRow); - return SpecializedCollections.SingletonEnumerable(testCase); + return [testCase]; } protected override IEnumerable CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) { var testCase = new WpfTheoryTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod); - return SpecializedCollections.SingletonEnumerable(testCase); + return [testCase]; } } } diff --git a/src/EditorFeatures/VisualBasic/LineCommit/CommitBufferManager.vb b/src/EditorFeatures/VisualBasic/LineCommit/CommitBufferManager.vb index d598f19fe2842..ab97764e5d05f 100644 --- a/src/EditorFeatures/VisualBasic/LineCommit/CommitBufferManager.vb +++ b/src/EditorFeatures/VisualBasic/LineCommit/CommitBufferManager.vb @@ -14,12 +14,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit ''' This class watches for buffer-based events, tracks the dirty regions, and invokes the formatter as appropriate ''' Partial Friend Class CommitBufferManager - Inherits ForegroundThreadAffinitizedObject Private ReadOnly _buffer As ITextBuffer Private ReadOnly _commitFormatter As ICommitFormatter Private ReadOnly _inlineRenameService As IInlineRenameService - Private _referencingViews As Integer ''' @@ -42,9 +40,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Public Sub New( buffer As ITextBuffer, commitFormatter As ICommitFormatter, - inlineRenameService As IInlineRenameService, - threadingContext As IThreadingContext) - MyBase.New(threadingContext, assertIsForeground:=False) + inlineRenameService As IInlineRenameService) Contract.ThrowIfNull(buffer) Contract.ThrowIfNull(commitFormatter) @@ -56,8 +52,6 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit End Sub Public Sub AddReferencingView() - ThisCanBeCalledOnAnyThread() - SyncLock _referencingViewsLock _referencingViews += 1 @@ -74,8 +68,6 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit End Sub Public Sub RemoveReferencingView() - ThisCanBeCalledOnAnyThread() - SyncLock _referencingViewsLock ' If someone enables line commit with a file already open, we might end up decrementing ' the ref count too many times, so only do work if we are still above 0. diff --git a/src/EditorFeatures/VisualBasic/LineCommit/CommitBufferManagerFactory.vb b/src/EditorFeatures/VisualBasic/LineCommit/CommitBufferManagerFactory.vb index 604b9ff80efc0..7bdc27c5faa1e 100644 --- a/src/EditorFeatures/VisualBasic/LineCommit/CommitBufferManagerFactory.vb +++ b/src/EditorFeatures/VisualBasic/LineCommit/CommitBufferManagerFactory.vb @@ -12,18 +12,16 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Friend Class CommitBufferManagerFactory Private ReadOnly _commitFormatter As ICommitFormatter Private ReadOnly _inlineRenameService As IInlineRenameService - Private ReadOnly _threadingContext As IThreadingContext Public Sub New(commitFormatter As ICommitFormatter, inlineRenameService As IInlineRenameService, threadingContext As IThreadingContext) _commitFormatter = commitFormatter _inlineRenameService = inlineRenameService - _threadingContext = threadingContext End Sub Public Function CreateForBuffer(buffer As ITextBuffer) As CommitBufferManager - Return buffer.Properties.GetOrCreateSingletonProperty(Function() New CommitBufferManager(buffer, _commitFormatter, _inlineRenameService, _threadingContext)) + Return buffer.Properties.GetOrCreateSingletonProperty(Function() New CommitBufferManager(buffer, _commitFormatter, _inlineRenameService)) End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/BraceMatching/BraceHighlightingTests.vb b/src/EditorFeatures/VisualBasicTest/BraceMatching/BraceHighlightingTests.vb index 374c6fb2dd999..e38c168775ee7 100644 --- a/src/EditorFeatures/VisualBasicTest/BraceMatching/BraceHighlightingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/BraceMatching/BraceHighlightingTests.vb @@ -36,7 +36,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.BraceMatching Dim doc = buffer.CurrentSnapshot.GetRelatedDocumentsWithChanges().FirstOrDefault() Dim context = New TaggerContext(Of BraceHighlightTag)( - doc, buffer.CurrentSnapshot, New SnapshotPoint(buffer.CurrentSnapshot, position)) + doc, buffer.CurrentSnapshot, frozenPartialSemantics:=False, New SnapshotPoint(buffer.CurrentSnapshot, position)) Await producer.GetTestAccessor().ProduceTagsAsync(context) Return context.TagSpans End Function diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb index e811c3dceb130..d6efc8e53c121 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb @@ -21,9 +21,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.Genera ' TODO Requires Wpf due to IInlineRenameService dependency (https: //github.com/dotnet/roslyn/issues/46153) Protected Overrides Function GetComposition() As TestComposition - Return EditorTestCompositions.EditorFeaturesWpf _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ - .AddParts(GetType(MockDiagnosticUpdateSourceRegistrationService)) + Return EditorTestCompositions.EditorFeaturesWpf End Function Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/Preview/PreviewTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/Preview/PreviewTests.vb index e0453e411f8f7..056450b1ef0d3 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/Preview/PreviewTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/Preview/PreviewTests.vb @@ -18,9 +18,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings Inherits AbstractVisualBasicCodeActionTest Private Shared ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeaturesWpf _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ .AddParts( - GetType(MockDiagnosticUpdateSourceRegistrationService), GetType(MockPreviewPaneService)) Private Const s_addedDocumentName As String = "AddedDocument" diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb index 7adcdeb7c3f4a..a4dc59fd809d3 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb @@ -847,7 +847,9 @@ Class C End Sub End Class " - Await VerifyItemsExistAsync(text, "Nothing", "True", "False", "Await") + For Each keywordKind In SyntaxFacts.GetKeywordKinds() + Await VerifyItemExistsAsync(text, SyntaxFacts.GetText(keywordKind), glyph:=Glyph.Keyword) + Next End Function diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb index fa8c764e075fa..4a19bc92bab85 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb @@ -14,9 +14,7 @@ Imports Microsoft.CodeAnalysis.UnitTests.Diagnostics <[UseExportProvider]> Public Class DiagnosticAnalyzerDriverTests - Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ - .AddParts(GetType(MockDiagnosticUpdateSourceRegistrationService)) + Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures Public Async Function DiagnosticAnalyzerDriverAllInOne() As Task diff --git a/src/EditorFeatures/VisualBasicTest/ObsoleteSymbol/VisualBasicObsoleteSymbolTests.vb b/src/EditorFeatures/VisualBasicTest/ObsoleteSymbol/VisualBasicObsoleteSymbolTests.vb new file mode 100644 index 0000000000000..fd6fee9bb0372 --- /dev/null +++ b/src/EditorFeatures/VisualBasicTest/ObsoleteSymbol/VisualBasicObsoleteSymbolTests.vb @@ -0,0 +1,218 @@ +' Licensed to the .NET Foundation under one or more 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.UnitTests.ObsoleteSymbol + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ObsoleteSymbol + Public Class VisualBasicObsoleteSymbolTests + Inherits AbstractObsoleteSymbolTests + + Protected Overrides Function CreateWorkspace(markup As String) As EditorTestWorkspace + Return EditorTestWorkspace.CreateVisualBasic(markup) + End Function + + + + + + + + Public Async Function TestObsoleteTypeDefinition(keyword As String) As Task + Await TestAsync( + $" + + {keyword} [|ObsoleteType|] + End {keyword} + + {keyword} NonObsoleteType + End {keyword} + ") + End Function + + + Public Async Function TestObsoleteDelegateTypeDefinition() As Task + Await TestAsync( + " + + Delegate Sub [|ObsoleteType|]() + + Delegate Sub NonObsoleteType() + ") + End Function + + + Public Async Function TestDeclarationAndUseOfObsoleteAlias() As Task + Await TestAsync( + " + Imports [|ObsoleteAlias|] = [|ObsoleteType|] + + + Class [|ObsoleteType|] + End Class + + ''' + ''' + Class NonObsoleteType + Dim field As [|ObsoleteAlias|] = New [|ObsoleteType|]() + End Class + ") + End Function + + + Public Async Function TestParametersAndReturnTypes() As Task + Await TestAsync( + " + + Class [|ObsoleteType|] + End Class + + Class NonObsoleteType + Function Method(arg As [|ObsoleteType|]) As [|ObsoleteType|] + Return New [|ObsoleteType|]() + End Function + + Dim field As System.Func(Of [|ObsoleteType|], [|ObsoleteType|]) = Function(arg As [|ObsoleteType|]) New [|ObsoleteType|]() + End Class + ") + End Function + + + Public Async Function TestImplicitType() As Task + Await TestAsync( + " + + Class [|ObsoleteType|] + Public Sub New() + End Sub + + + Public Sub New(x As Integer) + End Sub + End Class + + Class ObsoleteCtor + Public Sub New() + End Sub + + + Public Sub New(x As Integer) + End Sub + End Class + + Class NonObsoleteType + Sub Method() + Dim t1 As New [|ObsoleteType|]() + Dim t2 As [|New|] [|ObsoleteType|](3) + [|Dim|] t3 = New [|ObsoleteType|]() + [|Dim|] t4 = [|New|] [|ObsoleteType|](3) + Dim t5 As [|ObsoleteType|] = New [|ObsoleteType|]() + Dim t6 As [|ObsoleteType|] = [|New|] [|ObsoleteType|](3) + [|Dim|] t7 = CreateObsoleteType() + Dim t8 = NameOf([|ObsoleteType|]) + + Dim u1 As New ObsoleteCtor() + Dim u2 As [|New|] ObsoleteCtor(3) + Dim u3 = New ObsoleteCtor() + Dim u4 = [|New|] ObsoleteCtor(3) + Dim u5 As ObsoleteCtor = New ObsoleteCtor() + Dim u6 As ObsoleteCtor = [|New|] ObsoleteCtor(3) + Dim u8 = NameOf(ObsoleteCtor) + End Sub + + Function CreateObsoleteType() As [|ObsoleteType|] + Return New [|ObsoleteType|]() + End Function + End Class + ") + End Function + + + Public Async Function TestDeclarators() As Task + Await TestAsync( + " + + Class [|ObsoleteType|] + End Class + + Class NonObsoleteType + Sub Method() + ' In this method, only t5 has an implicit type, but the Dim keyword applies to all declared + ' variables. Currently this feature does not analyze a Dim keyword when more than one variable + ' is declared. + Dim t1, t2 As New [|ObsoleteType|](), t3, t4 As [|ObsoleteType|], t5 = New [|ObsoleteType|]() + End Sub + End Class + ") + End Function + + + Public Async Function TestExtensionMethods() As Task + Await TestAsync( + " + + Module [|ObsoleteType|] + + Public Shared Sub ObsoleteMember1(ignored As C) + End Sub + + + + Public Shared Sub [|ObsoleteMember2|](ignored As C) + End Sub + End Module + + Class C + Sub Method() + Me.ObsoleteMember1() + Me.[|ObsoleteMember2|]() + [|ObsoleteType|].ObsoleteMember1(Me) + [|ObsoleteType|].[|ObsoleteMember2|](Me) + End Sub + End Class + ") + End Function + + + Public Async Function TestGenerics() As Task + Await TestAsync( + " + + Class [|ObsoleteType|] + End Class + + + Structure [|ObsoleteValueType|] + End Structure + + Class G(Of T) + End Class + + Class C + Sub M(Of T)() + End Sub + + ''' + ''' Visual Basic, unlike C#, resolves concrete type names in generic argument positions in doc + ''' comment references. + ''' + ''' + Sub Method() + Dim x1 = New G(Of [|ObsoleteType|])() + Dim x2 = New G(Of G(Of [|ObsoleteType|]))() + M(Of [|ObsoleteType|])() + M(Of G(Of [|ObsoleteType|]))() + M(Of G(Of G(Of [|ObsoleteType|])))() + + ' Mark 'Dim' as obsolete even when it points to Nullable(Of T) where T is obsolete + [|Dim|] nullableValue = CreateNullableValueType() + End Sub + + Function CreateNullableValueType() As [|ObsoleteValueType|]? + Return New [|ObsoleteValueType|]() + End Function + End Class + ") + End Function + End Class +End Namespace diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs index 2e64dded07bb1..6648928c9825f 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs @@ -3906,7 +3906,7 @@ static void DummySequencePoint() { } }"; - var references = TargetFrameworkUtil.Mscorlib461ExtendedReferences.Concat(new[] { Net461.SystemThreadingTasks, TestMetadata.SystemThreadingTasksExtensions.PortableLib }); + var references = TargetFrameworkUtil.Mscorlib461ExtendedReferences.Concat(new[] { Net461.References.SystemThreadingTasks, TestMetadata.SystemThreadingTasksExtensions.PortableLib }); var comp = CreateEmptyCompilation(new[] { source, AsyncStreamsTypes }, references: references); WithRuntimeInstance(comp, references, runtime => { diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/Rewriters/LocalDeclarationRewriter.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/Rewriters/LocalDeclarationRewriter.vb index ae6db766bd0ca..7c0b9a9af9400 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/Rewriters/LocalDeclarationRewriter.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/Rewriters/LocalDeclarationRewriter.vb @@ -68,7 +68,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator ' CreateVariable(type As Type, name As String) Dim method = PlaceholderLocalSymbol.GetIntrinsicMethod(compilation, ExpressionCompilerConstants.CreateVariableMethodName) - Dim type = New BoundGetType(syntax, New BoundTypeExpression(syntax, local.Type), typeType) + Dim type = New BoundGetType(syntax, New BoundTypeExpression(syntax, local.Type), + DirectCast(compilation.GetWellKnownTypeMember(WellKnownMember.System_Type__GetTypeFromHandle), MethodSymbol), + typeType) Dim name = New BoundLiteral(syntax, ConstantValue.Create(local.Name), stringType) Dim customTypeInfoPayloadId = New BoundObjectCreationExpression(syntax, Nothing, ImmutableArray(Of BoundExpression).Empty, Nothing, guidType) Dim customTypeInfoPayload = New BoundLiteral(syntax, ConstantValue.Null, byteArrayType) diff --git a/src/Features/CSharp/Portable/AddImport/CSharpAddImportFeatureService.cs b/src/Features/CSharp/Portable/AddImport/CSharpAddImportFeatureService.cs index 3e6f09860dc1e..615ff65c71384 100644 --- a/src/Features/CSharp/Portable/AddImport/CSharpAddImportFeatureService.cs +++ b/src/Features/CSharp/Portable/AddImport/CSharpAddImportFeatureService.cs @@ -13,8 +13,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.AddImport; -using Microsoft.CodeAnalysis.CodeGeneration; -using Microsoft.CodeAnalysis.CSharp.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -23,7 +21,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -31,6 +28,9 @@ namespace Microsoft.CodeAnalysis.CSharp.AddImport; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportLanguageService(typeof(IAddImportFeatureService), LanguageNames.CSharp), Shared] internal class CSharpAddImportFeatureService : AbstractAddImportFeatureService { @@ -386,7 +386,7 @@ protected override async Task AddImportAsync( { var root = GetCompilationUnitSyntaxNode(contextNode, cancellationToken); - var usingDirective = SyntaxFactory.UsingDirective( + var usingDirective = UsingDirective( CreateNameSyntax(namespaceParts, namespaceParts.Count - 1)); var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); @@ -406,10 +406,10 @@ private static NameSyntax CreateNameSyntax(IReadOnlyList namespaceParts, part = "@" + part; } - var namePiece = SyntaxFactory.IdentifierName(part); + var namePiece = IdentifierName(part); return index == 0 ? namePiece - : SyntaxFactory.QualifiedName(CreateNameSyntax(namespaceParts, index - 1), namePiece); + : QualifiedName(CreateNameSyntax(namespaceParts, index - 1), namePiece); } private static (ExternAliasDirectiveSyntax, bool hasExistingImport) GetExternAliasDirective( @@ -423,7 +423,7 @@ private static (ExternAliasDirectiveSyntax, bool hasExistingImport) GetExternAli return (null, false); } - return (SyntaxFactory.ExternAliasDirective(SyntaxFactory.Identifier(val)) + return (ExternAliasDirective(Identifier(val)) .WithAdditionalAnnotations(Formatter.Annotation), hasExistingExtern); } @@ -447,7 +447,7 @@ private static (UsingDirectiveSyntax, bool hasExistingImport) GetUsingDirective( // from it if necessary). So we first create a dummy using directive just to // determine which container we're going in. Then we'll use the container to // help create the final using. - var dummyUsing = SyntaxFactory.UsingDirective(nameSyntax); + var dummyUsing = UsingDirective(nameSyntax); var container = addImportService.GetImportContainer(root, contextNode, dummyUsing, options); var namespaceToAddTo = container as BaseNamespaceDeclarationSyntax; @@ -460,7 +460,7 @@ private static (UsingDirectiveSyntax, bool hasExistingImport) GetUsingDirective( var externAlias = externAliasDirective?.Identifier.ValueText; if (externAlias != null) { - nameSyntax = AddOrReplaceAlias(nameSyntax, SyntaxFactory.IdentifierName(externAlias)); + nameSyntax = AddOrReplaceAlias(nameSyntax, IdentifierName(externAlias)); } else { @@ -476,12 +476,12 @@ private static (UsingDirectiveSyntax, bool hasExistingImport) GetUsingDirective( nameSyntax = RemoveGlobalAliasIfUnnecessary(semanticModel, nameSyntax, namespaceToAddTo); } - var usingDirective = SyntaxFactory.UsingDirective(nameSyntax) + var usingDirective = UsingDirective(nameSyntax) .WithAdditionalAnnotations(Formatter.Annotation); usingDirective = namespaceOrTypeSymbol.IsKind(SymbolKind.Namespace) ? usingDirective - : usingDirective.WithStaticKeyword(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + : usingDirective.WithStaticKeyword(StaticKeyword); return (usingDirective, addImportService.HasExistingImport(semanticModel.Compilation, root, contextNode, usingDirective, generator)); } @@ -537,7 +537,7 @@ private static NameSyntax AddOrReplaceAlias( { if (nameSyntax is SimpleNameSyntax simpleName) { - return SyntaxFactory.AliasQualifiedName(alias, simpleName); + return AliasQualifiedName(alias, simpleName); } if (nameSyntax is QualifiedNameSyntax qualifiedName) diff --git a/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs index cadacf5d61fcd..f01fef914a806 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs @@ -117,7 +117,7 @@ private static bool ContainsOnlyWhitespace(SourceText text, int openingPosition, // Handling syntax tree directly to avoid parsing in potentially UI blocking code-path var closingToken = FindClosingBraceToken(document.Root, closingPoint); var annotatedNewline = SyntaxFactory.EndOfLine(options.FormattingOptions.NewLine).WithAdditionalAnnotations(s_closingBraceNewlineAnnotation); - var newClosingToken = closingToken.WithPrependedLeadingTrivia(SpecializedCollections.SingletonEnumerable(annotatedNewline)); + var newClosingToken = closingToken.WithPrependedLeadingTrivia(annotatedNewline); var rootToFormat = document.Root.ReplaceToken(closingToken, newClosingToken); annotatedNewline = rootToFormat.GetAnnotatedTrivia(s_closingBraceNewlineAnnotation).Single(); @@ -183,7 +183,7 @@ static ImmutableArray GetMergedChanges(TextChange? newLineEdit, Immu [newLineEdit.Value.ToTextChangeRange()], formattingChanges.SelectAsArray(f => f.ToTextChangeRange())); - using var _ = ArrayBuilder.GetInstance(out var mergedChanges); + var mergedChanges = new FixedSizeArrayBuilder(newRanges.Length); var amountToShift = 0; foreach (var newRange in newRanges) { @@ -200,7 +200,7 @@ static ImmutableArray GetMergedChanges(TextChange? newLineEdit, Immu mergedChanges.Add(new TextChange(newTextChangeSpan, newTextChangeText)); } - return mergedChanges.ToImmutable(); + return mergedChanges.MoveToImmutable(); } } @@ -245,7 +245,7 @@ static ImmutableArray GetMergedChanges(TextChange? newLineEdit, Immu var annotatedRoot = GetSyntaxRootWithAnnotatedClosingBrace(document.Root, closingPoint); var result = Formatter.GetFormattingResult( - annotatedRoot, SpecializedCollections.SingletonEnumerable(spanToFormat), document.SolutionServices, options.FormattingOptions, rules, cancellationToken); + annotatedRoot, [spanToFormat], document.SolutionServices, options.FormattingOptions, rules, cancellationToken); if (result == null) { diff --git a/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs index 35e12b8a3042d..db36243d8bd22 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs @@ -74,7 +74,7 @@ public override void AddAlignTokensOperations(List list, S // For list patterns we format brackets as though they are a block, so ensure the close bracket // is aligned with the open bracket AddAlignIndentationOfTokensToBaseTokenOperation(list, node, bracketPair.openBracket, - SpecializedCollections.SingletonEnumerable(bracketPair.closeBracket), AlignTokensOption.AlignIndentationOfTokensToFirstTokenOfBaseTokenLine); + [bracketPair.closeBracket], AlignTokensOption.AlignIndentationOfTokensToFirstTokenOfBaseTokenLine); } } } diff --git a/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs index 6551e0b13d9bd..98ab13f19a3f9 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs @@ -260,7 +260,7 @@ public override void AddAlignTokensOperations(List list, S // If the user has set block style indentation and we're in a valid brace pair // then make sure we align the close brace to the open brace. AddAlignIndentationOfTokensToBaseTokenOperation(list, node, bracePair.openBrace, - SpecializedCollections.SingletonEnumerable(bracePair.closeBrace), AlignTokensOption.AlignIndentationOfTokensToFirstTokenOfBaseTokenLine); + [bracePair.closeBrace], AlignTokensOption.AlignIndentationOfTokensToFirstTokenOfBaseTokenLine); } } } diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx index 42585a2a7411a..13d4e8e6195ea 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx @@ -621,4 +621,8 @@ Warning: AI suggestions might be inaccurate. + + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + \ No newline at end of file diff --git a/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs b/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs index 9aa089ce5b27f..e12e26279379c 100644 --- a/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs +++ b/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs @@ -30,6 +30,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ChangeSignature; +using static CSharpSyntaxTokens; + [ExportLanguageService(typeof(AbstractChangeSignatureService), LanguageNames.CSharp), Shared] internal sealed class CSharpChangeSignatureService : AbstractChangeSignatureService { @@ -761,7 +763,7 @@ private ImmutableArray TransferLeadingWhitespaceTrivia(IEnumerable n index++; } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private async ValueTask> UpdateParamTagsInLeadingTriviaAsync( @@ -916,7 +918,7 @@ protected override bool SupportsOptionalAndParamsArrayParametersSimultaneously() } protected override SyntaxToken CommaTokenWithElasticSpace() - => Token(SyntaxKind.CommaToken).WithTrailingTrivia(ElasticSpace); + => CommaToken.WithTrailingTrivia(ElasticSpace); protected override bool TryGetRecordPrimaryConstructor(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor) => typeSymbol.TryGetPrimaryConstructor(out primaryConstructor); diff --git a/src/Features/CSharp/Portable/CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs b/src/Features/CSharp/Portable/CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs index b7fb93904a8dc..5f4a347ab3c4d 100644 --- a/src/Features/CSharp/Portable/CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/CodeFixes/GenerateType/GenerateTypeCodeFixProvider.cs @@ -47,12 +47,10 @@ protected override bool IsCandidate(SyntaxNode node, SyntaxToken token, Diagnost { switch (node) { - case QualifiedNameSyntax _: + case QualifiedNameSyntax or MemberAccessExpressionSyntax: return true; case SimpleNameSyntax simple: return !simple.IsParentKind(SyntaxKind.QualifiedName); - case MemberAccessExpressionSyntax _: - return true; } return false; diff --git a/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs b/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs index e1d5eb92ca431..8b19986f0c561 100644 --- a/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs @@ -19,10 +19,12 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; -using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.Suppression; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportConfigurationFixProvider(PredefinedConfigurationFixProviderNames.Suppression, LanguageNames.CSharp), Shared] internal class CSharpSuppressionCodeFixProvider : AbstractSuppressionCodeFixProvider { @@ -34,14 +36,14 @@ public CSharpSuppressionCodeFixProvider() protected override SyntaxTriviaList CreatePragmaRestoreDirectiveTrivia(Diagnostic diagnostic, Func formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine, CancellationToken cancellationToken) { - var restoreKeyword = SyntaxFactory.Token(SyntaxKind.RestoreKeyword); + var restoreKeyword = RestoreKeyword; return CreatePragmaDirectiveTrivia(restoreKeyword, diagnostic, formatNode, needsLeadingEndOfLine, needsTrailingEndOfLine, cancellationToken); } protected override SyntaxTriviaList CreatePragmaDisableDirectiveTrivia( Diagnostic diagnostic, Func formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine, CancellationToken cancellationToken) { - var disableKeyword = SyntaxFactory.Token(SyntaxKind.DisableKeyword); + var disableKeyword = DisableKeyword; return CreatePragmaDirectiveTrivia(disableKeyword, diagnostic, formatNode, needsLeadingEndOfLine, needsTrailingEndOfLine, cancellationToken); } @@ -49,18 +51,18 @@ private static SyntaxTriviaList CreatePragmaDirectiveTrivia( SyntaxToken disableOrRestoreKeyword, Diagnostic diagnostic, Func formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine, CancellationToken cancellationToken) { var diagnosticId = GetOrMapDiagnosticId(diagnostic, out var includeTitle); - var id = SyntaxFactory.IdentifierName(diagnosticId); + var id = IdentifierName(diagnosticId); var ids = new SeparatedSyntaxList().Add(id); - var pragmaDirective = SyntaxFactory.PragmaWarningDirectiveTrivia(disableOrRestoreKeyword, ids, true); + var pragmaDirective = PragmaWarningDirectiveTrivia(disableOrRestoreKeyword, ids, true); pragmaDirective = (PragmaWarningDirectiveTriviaSyntax)formatNode(pragmaDirective, cancellationToken); - var pragmaDirectiveTrivia = SyntaxFactory.Trivia(pragmaDirective); - var endOfLineTrivia = SyntaxFactory.CarriageReturnLineFeed; - var triviaList = SyntaxFactory.TriviaList(pragmaDirectiveTrivia); + var pragmaDirectiveTrivia = Trivia(pragmaDirective); + var endOfLineTrivia = CarriageReturnLineFeed; + var triviaList = TriviaList(pragmaDirectiveTrivia); var title = includeTitle ? diagnostic.Descriptor.Title.ToString(CultureInfo.CurrentUICulture) : null; if (!string.IsNullOrWhiteSpace(title)) { - var titleComment = SyntaxFactory.Comment(string.Format(" // {0}", title)).WithAdditionalAnnotations(Formatter.Annotation); + var titleComment = Comment(string.Format(" // {0}", title)).WithAdditionalAnnotations(Formatter.Annotation); triviaList = triviaList.Add(titleComment); } @@ -119,7 +121,7 @@ protected override SyntaxNode AddGlobalSuppressMessageAttribute( leadingTrivia: default)); if (isFirst && !newRoot.HasLeadingTrivia) - compilationRoot = compilationRoot.WithLeadingTrivia(SyntaxFactory.Comment(GlobalSuppressionsFileHeaderComment)); + compilationRoot = compilationRoot.WithLeadingTrivia(Comment(GlobalSuppressionsFileHeaderComment)); return compilationRoot; } @@ -156,17 +158,17 @@ private static AttributeListSyntax CreateAttributeList( var attributeArguments = CreateAttributeArguments(targetSymbol, diagnostic, isAssemblyAttribute); var attributes = new SeparatedSyntaxList() - .Add(SyntaxFactory.Attribute(attributeName, attributeArguments)); + .Add(Attribute(attributeName, attributeArguments)); AttributeListSyntax attributeList; if (isAssemblyAttribute) { - var targetSpecifier = SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.AssemblyKeyword)); - attributeList = SyntaxFactory.AttributeList(targetSpecifier, attributes); + var targetSpecifier = AttributeTargetSpecifier(AssemblyKeyword); + attributeList = AttributeList(targetSpecifier, attributes); } else { - attributeList = SyntaxFactory.AttributeList(attributes); + attributeList = AttributeList(attributes); } return attributeList.WithLeadingTrivia(leadingTrivia); @@ -175,30 +177,30 @@ private static AttributeListSyntax CreateAttributeList( private static AttributeArgumentListSyntax CreateAttributeArguments(ISymbol targetSymbol, Diagnostic diagnostic, bool isAssemblyAttribute) { // SuppressMessage("Rule Category", "Rule Id", Justification = nameof(Justification), Scope = nameof(Scope), Target = nameof(Target)) - var category = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(diagnostic.Descriptor.Category)); - var categoryArgument = SyntaxFactory.AttributeArgument(category); + var category = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(diagnostic.Descriptor.Category)); + var categoryArgument = AttributeArgument(category); var title = diagnostic.Descriptor.Title.ToString(CultureInfo.CurrentUICulture); var ruleIdText = string.IsNullOrWhiteSpace(title) ? diagnostic.Id : string.Format("{0}:{1}", diagnostic.Id, title); - var ruleId = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(ruleIdText)); - var ruleIdArgument = SyntaxFactory.AttributeArgument(ruleId); + var ruleId = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(ruleIdText)); + var ruleIdArgument = AttributeArgument(ruleId); - var justificationExpr = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(FeaturesResources.Pending)); - var justificationArgument = SyntaxFactory.AttributeArgument(SyntaxFactory.NameEquals("Justification"), nameColon: null, expression: justificationExpr); + var justificationExpr = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(FeaturesResources.Pending)); + var justificationArgument = AttributeArgument(NameEquals("Justification"), nameColon: null, expression: justificationExpr); - var attributeArgumentList = SyntaxFactory.AttributeArgumentList().AddArguments(categoryArgument, ruleIdArgument, justificationArgument); + var attributeArgumentList = AttributeArgumentList().AddArguments(categoryArgument, ruleIdArgument, justificationArgument); if (isAssemblyAttribute) { var scopeString = GetScopeString(targetSymbol.Kind); if (scopeString != null) { - var scopeExpr = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(scopeString)); - var scopeArgument = SyntaxFactory.AttributeArgument(SyntaxFactory.NameEquals("Scope"), nameColon: null, expression: scopeExpr); + var scopeExpr = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(scopeString)); + var scopeArgument = AttributeArgument(NameEquals("Scope"), nameColon: null, expression: scopeExpr); var targetString = GetTargetString(targetSymbol); - var targetExpr = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(targetString)); - var targetArgument = SyntaxFactory.AttributeArgument(SyntaxFactory.NameEquals("Target"), nameColon: null, expression: targetExpr); + var targetExpr = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(targetString)); + var targetArgument = AttributeArgument(NameEquals("Target"), nameColon: null, expression: targetExpr); attributeArgumentList = attributeArgumentList.AddArguments(scopeArgument, targetArgument); } @@ -237,9 +239,9 @@ protected override SyntaxTrivia TogglePragmaDirective(SyntaxTrivia trivia) var pragmaWarning = (PragmaWarningDirectiveTriviaSyntax)trivia.GetStructure(); var currentKeyword = pragmaWarning.DisableOrRestoreKeyword; var toggledKeywordKind = currentKeyword.Kind() == SyntaxKind.DisableKeyword ? SyntaxKind.RestoreKeyword : SyntaxKind.DisableKeyword; - var toggledToken = SyntaxFactory.Token(currentKeyword.LeadingTrivia, toggledKeywordKind, currentKeyword.TrailingTrivia); + var toggledToken = Token(currentKeyword.LeadingTrivia, toggledKeywordKind, currentKeyword.TrailingTrivia); var newPragmaWarning = pragmaWarning.WithDisableOrRestoreKeyword(toggledToken); - return SyntaxFactory.Trivia(newPragmaWarning); + return Trivia(newPragmaWarning); } protected override SyntaxNode GetContainingStatement(SyntaxToken token) diff --git a/src/Features/CSharp/Portable/CodeLens/CSharpCodeLensMemberFinder.cs b/src/Features/CSharp/Portable/CodeLens/CSharpCodeLensMemberFinder.cs index 8f8c4fbfafd7e..6987f945cf723 100644 --- a/src/Features/CSharp/Portable/CodeLens/CSharpCodeLensMemberFinder.cs +++ b/src/Features/CSharp/Portable/CodeLens/CSharpCodeLensMemberFinder.cs @@ -33,7 +33,7 @@ public async Task> GetCodeLensMembersAsync(Docume visitor.Visit(root); - return codeLensNodes.ToImmutable(); + return codeLensNodes.ToImmutableAndClear(); } private sealed class CSharpCodeLensVisitor(ArrayBuilder memberBuilder) : CSharpSyntaxWalker diff --git a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs index dbcd36b4ab619..ae26da1577ddb 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs @@ -16,6 +16,8 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.EnableNullable; +using static CSharpSyntaxTokens; + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.EnableNullable), Shared] internal partial class EnableNullableCodeRefactoringProvider : CodeRefactoringProvider { @@ -133,12 +135,12 @@ private static (SyntaxNode root, SyntaxToken firstToken) RewriteExistingDirectiv if (originalNode.SettingToken.IsKind(SyntaxKind.RestoreKeyword)) { - return rewrittenNode.WithSettingToken(SyntaxFactory.Token(SyntaxKind.DisableKeyword).WithTriviaFrom(rewrittenNode.SettingToken)); + return rewrittenNode.WithSettingToken(DisableKeyword.WithTriviaFrom(rewrittenNode.SettingToken)); } if (originalNode.SettingToken.IsKind(SyntaxKind.EnableKeyword)) { - return rewrittenNode.WithSettingToken(SyntaxFactory.Token(SyntaxKind.RestoreKeyword).WithTriviaFrom(rewrittenNode.SettingToken)); + return rewrittenNode.WithSettingToken(RestoreKeyword.WithTriviaFrom(rewrittenNode.SettingToken)); } Debug.Fail("Unexpected state?"); @@ -156,7 +158,7 @@ private static async Task DisableNullableReferenceTypesInExistingDoc // Add a new '#nullable disable' to the top of each file if (!HasLeadingNullableDirective(root, out var leadingDirective)) { - var nullableDisableTrivia = SyntaxFactory.Trivia(SyntaxFactory.NullableDirectiveTrivia(SyntaxFactory.Token(SyntaxKind.DisableKeyword).WithPrependedLeadingTrivia(SyntaxFactory.ElasticSpace), isActive: true)); + var nullableDisableTrivia = SyntaxFactory.Trivia(SyntaxFactory.NullableDirectiveTrivia(DisableKeyword.WithPrependedLeadingTrivia(SyntaxFactory.ElasticSpace), isActive: true)); var existingTriviaList = firstToken.LeadingTrivia; var insertionIndex = GetInsertionPoint(existingTriviaList); diff --git a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs index 6ade75c9f0742..70fe81e5a6251 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs @@ -23,6 +23,9 @@ namespace Microsoft.CodeAnalysis.CSharp.ChangeNamespace; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportLanguageService(typeof(IChangeNamespaceService), LanguageNames.CSharp), Shared] internal sealed class CSharpChangeNamespaceService : AbstractChangeNamespaceService @@ -139,7 +142,7 @@ public override bool TryGetReplacementReferenceSyntax( if (!TryGetGlobalQualifiedName(newNamespaceParts, nameRef, aliasQualifier, out newNode)) { var qualifiedNamespaceName = CreateNamespaceAsQualifiedName(newNamespaceParts, aliasQualifier, newNamespaceParts.Length - 1); - newNode = SyntaxFactory.QualifiedName(qualifiedNamespaceName, nameRef.WithoutTrivia()); + newNode = QualifiedName(qualifiedNamespaceName, nameRef.WithoutTrivia()); } // We might lose some trivia associated with children of `oldNode`. @@ -156,7 +159,7 @@ public override bool TryGetReplacementReferenceSyntax( if (!TryGetGlobalQualifiedName(newNamespaceParts, nameRef, aliasQualifier, out newNode)) { var memberAccessNamespaceName = CreateNamespaceAsMemberAccess(newNamespaceParts, aliasQualifier, newNamespaceParts.Length - 1); - newNode = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, memberAccessNamespaceName, nameRef.WithoutTrivia()); + newNode = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, memberAccessNamespaceName, nameRef.WithoutTrivia()); } // We might lose some trivia associated with children of `oldNode`. @@ -179,7 +182,7 @@ public override bool TryGetReplacementReferenceSyntax( // We will replace entire `QualifiedCrefSyntax` with a `TypeCrefSyntax`, // which is a alias qualified simple name, similar to the regular case above. oldNode = qualifiedCref; - newNode = SyntaxFactory.TypeCref((AliasQualifiedNameSyntax)newNode!); + newNode = TypeCref((AliasQualifiedNameSyntax)newNode!); } else { @@ -208,8 +211,8 @@ private static bool TryGetGlobalQualifiedName( { // If new namespace is "", then name will be declared in global namespace. // We will replace qualified reference with simple name qualified with alias (global if it's not alias qualified) - var aliasNode = aliasQualifier?.ToIdentifierName() ?? SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword)); - newNode = SyntaxFactory.AliasQualifiedName(aliasNode, nameNode.WithoutTrivia()); + var aliasNode = aliasQualifier?.ToIdentifierName() ?? IdentifierName(GlobalKeyword); + newNode = AliasQualifiedName(aliasNode, nameNode.WithoutTrivia()); return true; } @@ -310,7 +313,7 @@ private static CompilationUnitSyntax MoveMembersFromGlobalToNamespace(Compilatio { Debug.Assert(!compilationUnit.Members.Any(m => m is BaseNamespaceDeclarationSyntax)); - var targetNamespaceDecl = SyntaxFactory.NamespaceDeclaration( + var targetNamespaceDecl = NamespaceDeclaration( name: CreateNamespaceAsQualifiedName(targetNamespaceParts, aliasQualifier: null, targetNamespaceParts.Length - 1) .WithAdditionalAnnotations(WarningAnnotation), externs: default, @@ -413,12 +416,12 @@ private static NameSyntax CreateNamespaceAsQualifiedName(ImmutableArray var part = namespaceParts[index].EscapeIdentifier(); Debug.Assert(part.Length > 0); - var namePiece = SyntaxFactory.IdentifierName(part); + var namePiece = IdentifierName(part); if (index == 0) - return aliasQualifier == null ? namePiece : SyntaxFactory.AliasQualifiedName(aliasQualifier, namePiece); + return aliasQualifier == null ? namePiece : AliasQualifiedName(aliasQualifier, namePiece); - return SyntaxFactory.QualifiedName(CreateNamespaceAsQualifiedName(namespaceParts, aliasQualifier, index - 1), namePiece); + return QualifiedName(CreateNamespaceAsQualifiedName(namespaceParts, aliasQualifier, index - 1), namePiece); } private static ExpressionSyntax CreateNamespaceAsMemberAccess(ImmutableArray namespaceParts, string? aliasQualifier, int index) @@ -426,16 +429,16 @@ private static ExpressionSyntax CreateNamespaceAsMemberAccess(ImmutableArray 0); - var namePiece = SyntaxFactory.IdentifierName(part); + var namePiece = IdentifierName(part); if (index == 0) { return aliasQualifier == null ? namePiece - : SyntaxFactory.AliasQualifiedName(aliasQualifier, namePiece); + : AliasQualifiedName(aliasQualifier, namePiece); } - return SyntaxFactory.MemberAccessExpression( + return MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, CreateNamespaceAsMemberAccess(namespaceParts, aliasQualifier, index - 1), namePiece); diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs index 61b65f0f72745..6e703ff629340 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AttributeNamedParameterCompletionProvider.cs @@ -228,7 +228,7 @@ private static IEnumerable> GetParameterLists( .Select(c => c.Parameters); } - return SpecializedCollections.EmptyEnumerable>(); + return []; } private static IEnumerable GetAttributeNamedParameters( diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs index aa5e0c25faf17..8a319d99b2242 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs @@ -76,7 +76,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var span = GetCompletionItemSpan(text, position); - var serializedOptions = ImmutableArray.Create(new KeyValuePair(HideAdvancedMembers, options.HideAdvancedMembers.ToString())); + var serializedOptions = ImmutableArray.Create(KeyValuePairUtil.Create(HideAdvancedMembers, options.HideAdvancedMembers.ToString())); var items = CreateCompletionItems(semanticModel, symbols, token, position, serializedOptions); diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs index e6f199978569a..60ebe77ff745b 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs @@ -30,7 +30,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal partial class DeclarationNameCompletionProvider([ImportMany] IEnumerable> recommenders) : LSPCompletionProvider { - private ImmutableArray> Recommenders { get; } = ExtensionOrderer.Order(recommenders).ToImmutableArray(); + private ImmutableArray> Recommenders { get; } = [.. ExtensionOrderer.Order(recommenders)]; internal override string Language => LanguageNames.CSharp; diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameRecommender.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameRecommender.cs index 3d88f7a352b7e..9051e654375c1 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameRecommender.cs @@ -60,7 +60,7 @@ public DeclarationNameRecommender() GetRecommendedNames(names, nameInfo, context, result, namingStyleOptions, cancellationToken); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private ImmutableArray> GetBaseNames(SemanticModel semanticModel, NameDeclarationInfo nameInfo) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.cs index 77f957b583757..baa51b17bc688 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/NamedParameterCompletionProvider.cs @@ -265,8 +265,7 @@ private static ISet GetExistingNamedParameters(BaseArgumentListSyntax ar } else if (expressionType.IsDelegateType()) { - var delegateType = expressionType; - return SpecializedCollections.SingletonEnumerable(delegateType.DelegateInvokeMethod!.Parameters); + return [expressionType.DelegateInvokeMethod!.Parameters]; } } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs index 8cc38ccdd5414..6f74c19cb4607 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs @@ -31,7 +31,7 @@ internal partial class UnnamedSymbolCompletionProvider /// private const string RehydrateName = "Rehydrate"; private static readonly ImmutableArray> s_conversionProperties = - [new KeyValuePair(KindName, ConversionKindName)]; + [KeyValuePairUtil.Create(KindName, ConversionKindName)]; // We set conversion items' match priority to "Deprioritize" so completion selects other symbols over it when user starts typing. // e.g. method symbol `Should` should be selected over `(short)` when "sh" is typed. @@ -67,8 +67,8 @@ private static (ImmutableArray symbols, ImmutableArray>.GetInstance(out var builder); builder.AddRange(s_conversionProperties); - builder.Add(new KeyValuePair(RehydrateName, RehydrateName)); - builder.Add(new KeyValuePair(DocumentationCommentXmlName, conversion.GetDocumentationCommentXml(cancellationToken: context.CancellationToken) ?? "")); + builder.Add(KeyValuePairUtil.Create(RehydrateName, RehydrateName)); + builder.Add(KeyValuePairUtil.Create(DocumentationCommentXmlName, conversion.GetDocumentationCommentXml(cancellationToken: context.CancellationToken) ?? "")); var symbols = ImmutableArray.Create(conversion.ContainingType, conversion.Parameters.First().Type, conversion.ReturnType); return (symbols, builder.ToImmutable()); } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Indexers.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Indexers.cs index caa0d240ab0dd..37e466baa923d 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Indexers.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Indexers.cs @@ -9,13 +9,14 @@ using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.LanguageService; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers; internal partial class UnnamedSymbolCompletionProvider { private readonly ImmutableArray> IndexerProperties = - [new KeyValuePair(KindName, IndexerKindName)]; + [KeyValuePairUtil.Create(KindName, IndexerKindName)]; private void AddIndexers(CompletionContext context, ImmutableArray indexers) { diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs index 1b0480be7bd30..5c0c652ec2e75 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs @@ -33,7 +33,7 @@ private enum OperatorPosition private readonly string OperatorName = nameof(OperatorName); private readonly ImmutableArray> OperatorProperties = - [new KeyValuePair(KindName, OperatorKindName)]; + [KeyValuePairUtil.Create(KindName, OperatorKindName)]; /// /// Ordered in the order we want to display operators in the completion list. @@ -113,7 +113,7 @@ private void AddOperatorGroup(CompletionContext context, string opName, IEnumera symbols: operators.ToImmutableArray(), rules: s_operatorRules, contextPosition: context.Position, - properties: [.. OperatorProperties, new KeyValuePair(OperatorName, opName)], + properties: [.. OperatorProperties, KeyValuePairUtil.Create(OperatorName, opName)], isComplexTextEdit: true)); } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/PartialTypeCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/PartialTypeCompletionProvider.cs index c804f80406573..797866ec2c60f 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/PartialTypeCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/PartialTypeCompletionProvider.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers; @@ -76,7 +77,7 @@ private static bool IsPartialTypeDeclaration(SyntaxNode syntax) => syntax is BaseTypeDeclarationSyntax declarationSyntax && declarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); protected override ImmutableArray> GetProperties(INamedTypeSymbol symbol, CSharpSyntaxContext context) - => [new KeyValuePair(InsertionTextOnLessThan, symbol.Name.EscapeIdentifier())]; + => [KeyValuePairUtil.Create(InsertionTextOnLessThan, symbol.Name.EscapeIdentifier())]; public override async Task GetTextChangeAsync( Document document, CompletionItem selectedItem, char? ch, CancellationToken cancellationToken) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs index d4528663391b0..6df55c5e6ee55 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Snippets; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; @@ -33,23 +34,24 @@ internal sealed class SnippetCompletionProvider : LSPCompletionProvider { private static readonly HashSet s_snippetsWithReplacements = [ - "class", - "cw", - "ctor", - "else", - "enum", - "for", - "forr", - "foreach", - "if", - "interface", - "lock", - "prop", - "propg", - "sim", - "struct", - "svm", - "while" + CSharpSnippetIdentifiers.Class, + CommonSnippetIdentifiers.ConsoleWriteLine, + CommonSnippetIdentifiers.Constructor, + CSharpSnippetIdentifiers.Do, + CSharpSnippetIdentifiers.Else, + CSharpSnippetIdentifiers.Enum, + CSharpSnippetIdentifiers.For, + CSharpSnippetIdentifiers.ReversedFor, + CSharpSnippetIdentifiers.ForEach, + CSharpSnippetIdentifiers.If, + CSharpSnippetIdentifiers.Interface, + CSharpSnippetIdentifiers.Lock, + CommonSnippetIdentifiers.Property, + CommonSnippetIdentifiers.GetOnlyProperty, + CSharpSnippetIdentifiers.StaticIntMain, + CSharpSnippetIdentifiers.Struct, + CSharpSnippetIdentifiers.StaticVoidMain, + CSharpSnippetIdentifiers.While ]; internal override bool IsSnippetProvider => true; diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs index b950e664dfc13..f4a7ef5437256 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; @@ -17,7 +16,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -37,6 +36,27 @@ public XmlDocCommentCompletionProvider() : base(s_defaultRules) { } + private static readonly ImmutableArray s_keywordNames; + + static XmlDocCommentCompletionProvider() + { + using var _ = ArrayBuilder.GetInstance(out var keywordsBuilder); + + foreach (var keywordKind in SyntaxFacts.GetKeywordKinds()) + { + var keywordText = SyntaxFacts.GetText(keywordKind); + + // There are several very special keywords like `__makeref`, which are not intended for pubic use. + // They all start with `_`, so we are filtering them here + if (keywordText[0] != '_') + { + keywordsBuilder.Add(keywordText); + } + } + + s_keywordNames = keywordsBuilder.ToImmutable(); + } + internal override string Language => LanguageNames.CSharp; public override bool IsInsertionTrigger(SourceText text, int characterPosition, CompletionOptions options) @@ -160,7 +180,7 @@ public override bool IsInsertionTrigger(SourceText text, int characterPosition, } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken, ErrorSeverity.General)) { - return SpecializedCollections.EmptyEnumerable(); + return []; } } @@ -313,18 +333,8 @@ private static bool IsAttributeValueContext(SyntaxToken token, [NotNullWhen(true return false; } - protected override IEnumerable GetKeywordNames() - { - yield return SyntaxFacts.GetText(SyntaxKind.NullKeyword); - yield return SyntaxFacts.GetText(SyntaxKind.StaticKeyword); - yield return SyntaxFacts.GetText(SyntaxKind.VirtualKeyword); - yield return SyntaxFacts.GetText(SyntaxKind.TrueKeyword); - yield return SyntaxFacts.GetText(SyntaxKind.FalseKeyword); - yield return SyntaxFacts.GetText(SyntaxKind.AbstractKeyword); - yield return SyntaxFacts.GetText(SyntaxKind.SealedKeyword); - yield return SyntaxFacts.GetText(SyntaxKind.AsyncKeyword); - yield return SyntaxFacts.GetText(SyntaxKind.AwaitKeyword); - } + protected override ImmutableArray GetKeywordNames() + => s_keywordNames; protected override IEnumerable GetExistingTopLevelElementNames(DocumentationCommentTriviaSyntax syntax) => syntax.Content.Select(GetElementName).WhereNotNull(); diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs index 288947677d05c..54e09fcf7e941 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs @@ -68,11 +68,17 @@ private static bool IsTypeParameterConstraintContext(CSharpSyntaxContext context // void Goo() | if (token.Kind() == SyntaxKind.CloseParenToken && - token.Parent.IsKind(SyntaxKind.ParameterList) && - token.Parent.IsParentKind(SyntaxKind.MethodDeclaration)) + token.Parent.IsKind(SyntaxKind.ParameterList)) { - var decl = token.GetAncestor(); - if (decl != null && decl.Arity > 0) + var tokenParent = token.Parent; + if (tokenParent.IsParentKind(SyntaxKind.MethodDeclaration, out var methodDeclaration)) + { + if (methodDeclaration.Arity > 0) + { + return true; + } + } + else if (tokenParent.Parent is LocalFunctionStatementSyntax { TypeParameterList.Parameters.Count: > 0 }) { return true; } diff --git a/src/Features/CSharp/Portable/Completion/Providers/OutVariableArgumentProvider.cs b/src/Features/CSharp/Portable/Completion/Providers/OutVariableArgumentProvider.cs index 825dfcc78c207..00cf305a680e5 100644 --- a/src/Features/CSharp/Portable/Completion/Providers/OutVariableArgumentProvider.cs +++ b/src/Features/CSharp/Portable/Completion/Providers/OutVariableArgumentProvider.cs @@ -10,6 +10,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportArgumentProvider(nameof(OutVariableArgumentProvider), LanguageNames.CSharp)] [ExtensionOrder(After = nameof(ContextVariableArgumentProvider))] [Shared] @@ -44,12 +47,12 @@ public override Task ProvideArgumentAsync(ArgumentContext context) name = "@" + name; } - var syntax = SyntaxFactory.Argument( + var syntax = Argument( nameColon: null, - refKindKeyword: SyntaxFactory.Token(SyntaxKind.OutKeyword), - SyntaxFactory.DeclarationExpression( - type: SyntaxFactory.IdentifierName("var"), - designation: SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier( + refKindKeyword: OutKeyword, + DeclarationExpression( + type: IdentifierName("var"), + designation: SingleVariableDesignation(Identifier( [], contextualKind: SyntaxKind.None, text: name, diff --git a/src/Features/CSharp/Portable/ConvertAnonymousType/CSharpConvertAnonymousTypeToClassCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertAnonymousType/CSharpConvertAnonymousTypeToClassCodeRefactoringProvider.cs index 5b3d77ee32f9a..59b705818da8f 100644 --- a/src/Features/CSharp/Portable/ConvertAnonymousType/CSharpConvertAnonymousTypeToClassCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertAnonymousType/CSharpConvertAnonymousTypeToClassCodeRefactoringProvider.cs @@ -12,6 +12,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertAnonymousType; +using static CSharpSyntaxTokens; + [ExtensionOrder(Before = PredefinedCodeRefactoringProviderNames.IntroduceVariable)] [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertAnonymousTypeToClass), Shared] internal class CSharpConvertAnonymousTypeToClassCodeRefactoringProvider : @@ -38,9 +40,9 @@ protected override ObjectCreationExpressionSyntax CreateObjectCreationExpression private ArgumentListSyntax CreateArgumentList(AnonymousObjectCreationExpressionSyntax anonymousObject) => SyntaxFactory.ArgumentList( - SyntaxFactory.Token(SyntaxKind.OpenParenToken).WithTriviaFrom(anonymousObject.OpenBraceToken), + OpenParenToken.WithTriviaFrom(anonymousObject.OpenBraceToken), CreateArguments(anonymousObject.Initializers), - SyntaxFactory.Token(SyntaxKind.CloseParenToken).WithTriviaFrom(anonymousObject.CloseBraceToken)); + CloseParenToken.WithTriviaFrom(anonymousObject.CloseBraceToken)); private SeparatedSyntaxList CreateArguments(SeparatedSyntaxList initializers) => SyntaxFactory.SeparatedList(CreateArguments(OmitTrailingComma(initializers.GetWithSeparators()))); diff --git a/src/Features/CSharp/Portable/ConvertAnonymousType/CSharpConvertAnonymousTypeToTupleCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertAnonymousType/CSharpConvertAnonymousTypeToTupleCodeRefactoringProvider.cs index 89747fef64d27..cf3d02bb5eee2 100644 --- a/src/Features/CSharp/Portable/ConvertAnonymousType/CSharpConvertAnonymousTypeToTupleCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertAnonymousType/CSharpConvertAnonymousTypeToTupleCodeRefactoringProvider.cs @@ -12,6 +12,9 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertAnonymousType; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertAnonymousTypeToTuple), Shared] internal class CSharpConvertAnonymousTypeToTupleCodeRefactoringProvider : AbstractConvertAnonymousTypeToTupleCodeRefactoringProvider< @@ -29,23 +32,23 @@ protected override int GetInitializerCount(AnonymousObjectCreationExpressionSynt => anonymousType.Initializers.Count; protected override TupleExpressionSyntax ConvertToTuple(AnonymousObjectCreationExpressionSyntax anonCreation) - => SyntaxFactory.TupleExpression( - SyntaxFactory.Token(SyntaxKind.OpenParenToken).WithTriviaFrom(anonCreation.OpenBraceToken), + => TupleExpression( + OpenParenToken.WithTriviaFrom(anonCreation.OpenBraceToken), ConvertInitializers(anonCreation.Initializers), - SyntaxFactory.Token(SyntaxKind.CloseParenToken).WithTriviaFrom(anonCreation.CloseBraceToken)) + CloseParenToken.WithTriviaFrom(anonCreation.CloseBraceToken)) .WithPrependedLeadingTrivia(anonCreation.GetLeadingTrivia()); private static SeparatedSyntaxList ConvertInitializers(SeparatedSyntaxList initializers) - => SyntaxFactory.SeparatedList(initializers.Select(ConvertInitializer), initializers.GetSeparators()); + => SeparatedList(initializers.Select(ConvertInitializer), initializers.GetSeparators()); private static ArgumentSyntax ConvertInitializer(AnonymousObjectMemberDeclaratorSyntax declarator) - => SyntaxFactory.Argument(ConvertName(declarator.NameEquals), default, declarator.Expression) + => Argument(ConvertName(declarator.NameEquals), default, declarator.Expression) .WithTriviaFrom(declarator); private static NameColonSyntax? ConvertName(NameEqualsSyntax? nameEquals) => nameEquals == null ? null - : SyntaxFactory.NameColon( + : NameColon( nameEquals.Name, - SyntaxFactory.Token(SyntaxKind.ColonToken).WithTriviaFrom(nameEquals.EqualsToken)); + ColonToken.WithTriviaFrom(nameEquals.EqualsToken)); } diff --git a/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs index ff57f22b3994c..aa099ad520612 100644 --- a/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertAutoPropertyToFullProperty/CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs @@ -23,6 +23,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertAutoPropertyToFullProperty; +using static CSharpSyntaxTokens; + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertAutoPropertyToFullProperty), Shared] internal class CSharpConvertAutoPropertyToFullPropertyCodeRefactoringProvider : AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider { @@ -103,9 +105,9 @@ private static SyntaxNode GetUpdatedAccessor(CSharpCodeGenerationContextInfo inf internal static SyntaxNode AddStatement(SyntaxNode accessor, SyntaxNode statement) { var blockSyntax = SyntaxFactory.Block( - SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed), + OpenBraceToken.WithLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed), new SyntaxList((StatementSyntax)statement), - SyntaxFactory.Token(SyntaxKind.CloseBraceToken) + CloseBraceToken .WithTrailingTrivia(((AccessorDeclarationSyntax)accessor).SemicolonToken.TrailingTrivia)); return ((AccessorDeclarationSyntax)accessor).WithBody(blockSyntax); diff --git a/src/Features/CSharp/Portable/ConvertCast/CSharpConvertTryCastToDirectCastCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertCast/CSharpConvertTryCastToDirectCastCodeRefactoringProvider.cs index e5e8ec880cc19..97925e803f8b8 100644 --- a/src/Features/CSharp/Portable/ConvertCast/CSharpConvertTryCastToDirectCastCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertCast/CSharpConvertTryCastToDirectCastCodeRefactoringProvider.cs @@ -12,6 +12,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertCast; +using static CSharpSyntaxTokens; + /// /// Refactor: /// var o = 1 as object; @@ -47,15 +49,15 @@ protected override CastExpressionSyntax ConvertExpression(BinaryExpressionSyntax // #0 #2 (Type)exp #1 #3 // Some trivia in the middle (#1 and #2) is moved to the front or behind the expression // #1 and #2 change their position in the expression (#2 goes in front to stay near the type and #1 to the end to stay near the expression) - var openParen = Token(SyntaxKind.OpenParenToken); - var closeParen = Token(SyntaxKind.CloseParenToken); + var openParen = OpenParenToken; + var closeParen = CloseParenToken; var newTrailingTrivia = asExpression.Left.GetTrailingTrivia().SkipInitialWhitespace().ToSyntaxTriviaList().AddRange(asExpression.GetTrailingTrivia()); var newLeadingTrivia = asExpression.GetLeadingTrivia().AddRange(asExpression.OperatorToken.TrailingTrivia.SkipInitialWhitespace()); typeNode = typeNode.WithoutTrailingTrivia(); // Make sure we make reference type nullable when converting expressions like `null as string` -> `(string?)null` if (expression.IsKind(SyntaxKind.NullLiteralExpression) && nullableContext.HasFlag(NullableContext.AnnotationsEnabled) && isReferenceType) - typeNode = NullableType(typeNode, Token(SyntaxKind.QuestionToken)); + typeNode = NullableType(typeNode, QuestionToken); var castExpression = CastExpression(openParen, typeNode, closeParen, expression.WithoutTrailingTrivia()) .WithLeadingTrivia(newLeadingTrivia) diff --git a/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs index a6f7e8c9a272d..cddfa8d70cd2b 100644 --- a/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertForEachToFor/CSharpConvertForEachToForCodeRefactoringProvider.cs @@ -18,6 +18,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertForEachToFor; +using static SyntaxFactory; + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertForEachToFor), Shared] internal sealed class CSharpConvertForEachToForCodeRefactoringProvider : AbstractConvertForEachToForCodeRefactoringProvider @@ -84,19 +86,19 @@ protected override void ConvertToForStatement( var bodyStatement = GetForLoopBody(generator, foreachInfo, collectionVariable, indexVariable, donotCastElement); // create for statement from foreach statement - var forStatement = SyntaxFactory.ForStatement( - SyntaxFactory.VariableDeclaration( + var forStatement = ForStatement( + VariableDeclaration( model.Compilation.GetSpecialType(SpecialType.System_Int32).GenerateTypeSyntax(), - [SyntaxFactory.VariableDeclarator( + [VariableDeclarator( indexVariable.WithAdditionalAnnotations(RenameAnnotation.Create()), argumentList: null, - SyntaxFactory.EqualsValueClause((ExpressionSyntax)generator.LiteralExpression(0)))]), + EqualsValueClause((ExpressionSyntax)generator.LiteralExpression(0)))]), initializers: [], (ExpressionSyntax)generator.LessThanExpression( generator.IdentifierName(indexVariable), generator.MemberAccessExpression(collectionVariable, foreachInfo.CountName)), - [SyntaxFactory.PostfixUnaryExpression( - SyntaxKind.PostIncrementExpression, SyntaxFactory.IdentifierName(indexVariable))], + [PostfixUnaryExpression( + SyntaxKind.PostIncrementExpression, IdentifierName(indexVariable))], bodyStatement); if (!foreachInfo.RequireCollectionStatement) @@ -127,7 +129,7 @@ private StatementSyntax GetForLoopBody( foreachStatement.Identifier, donotCastElement ? null : foreachInfo.ForEachElementType, collectionVariableName, indexVariable); - var bodyBlock = foreachStatement.Statement is BlockSyntax block ? block : SyntaxFactory.Block(foreachStatement.Statement); + var bodyBlock = foreachStatement.Statement is BlockSyntax block ? block : Block(foreachStatement.Statement); if (bodyBlock.Statements.Count == 0) { // If the block was empty, still put the new variable inside of it. This handles the case where the user @@ -144,7 +146,7 @@ private StatementSyntax GetForLoopBody( return bodyBlock.InsertNodesBefore( bodyBlock.Statements[0], - SpecializedCollections.SingletonEnumerable(variableStatement)); + [variableStatement]); } } diff --git a/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs index 5cf54331654f3..0cd4d5e089846 100644 --- a/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertForToForEach/CSharpConvertForToForEachCodeRefactoringProvider.cs @@ -12,6 +12,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertForToForEach; +using static CSharpSyntaxTokens; + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertForToForEach), Shared] internal class CSharpConvertForToForEachCodeRefactoringProvider : AbstractConvertForToForEachCodeRefactoringProvider< @@ -117,11 +119,11 @@ protected override SyntaxNode ConvertForNode( typeNode ??= iterationVariableType.GenerateTypeSyntax(); return SyntaxFactory.ForEachStatement( - SyntaxFactory.Token(SyntaxKind.ForEachKeyword).WithTriviaFrom(forStatement.ForKeyword), + ForEachKeyword.WithTriviaFrom(forStatement.ForKeyword), forStatement.OpenParenToken, typeNode, foreachIdentifier, - SyntaxFactory.Token(SyntaxKind.InKeyword), + InKeyword, collectionExpression, forStatement.CloseParenToken, forStatement.Statement); diff --git a/src/Features/CSharp/Portable/ConvertIfToSwitch/CSharpConvertIfToSwitchCodeRefactoringProvider.Rewriting.cs b/src/Features/CSharp/Portable/ConvertIfToSwitch/CSharpConvertIfToSwitchCodeRefactoringProvider.Rewriting.cs index b308de1dd5e14..cd1028b1bfe08 100644 --- a/src/Features/CSharp/Portable/ConvertIfToSwitch/CSharpConvertIfToSwitchCodeRefactoringProvider.Rewriting.cs +++ b/src/Features/CSharp/Portable/ConvertIfToSwitch/CSharpConvertIfToSwitchCodeRefactoringProvider.Rewriting.cs @@ -15,6 +15,7 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertIfToSwitch; +using static CSharpSyntaxTokens; using static SyntaxFactory; internal sealed partial class CSharpConvertIfToSwitchCodeRefactoringProvider @@ -69,13 +70,13 @@ public override SyntaxNode CreateSwitchStatement(IfStatementSyntax ifStatement, { var block = ifStatement.Statement as BlockSyntax; return SwitchStatement( - switchKeyword: Token(SyntaxKind.SwitchKeyword).WithTriviaFrom(ifStatement.IfKeyword), + switchKeyword: SwitchKeyword.WithTriviaFrom(ifStatement.IfKeyword), openParenToken: ifStatement.OpenParenToken, expression: (ExpressionSyntax)expression, closeParenToken: ifStatement.CloseParenToken.WithPrependedLeadingTrivia(ElasticMarker), - openBraceToken: block?.OpenBraceToken ?? Token(SyntaxKind.OpenBraceToken), + openBraceToken: block?.OpenBraceToken ?? OpenBraceToken, sections: [.. sectionList.Cast()], - closeBraceToken: block?.CloseBraceToken.WithoutLeadingTrivia() ?? Token(SyntaxKind.CloseBraceToken)); + closeBraceToken: block?.CloseBraceToken.WithoutLeadingTrivia() ?? CloseBraceToken); } private static WhenClauseSyntax? AsWhenClause(AnalyzedSwitchLabel label) @@ -90,7 +91,7 @@ public override SyntaxNode AsSwitchLabelSyntax(AnalyzedSwitchLabel label, Featur => CasePatternSwitchLabel( AsPatternSyntax(label.Pattern, feature), AsWhenClause(label), - Token(SyntaxKind.ColonToken)); + ColonToken); private static PatternSyntax AsPatternSyntax(AnalyzedPattern pattern, Feature feature) => pattern switch diff --git a/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs b/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs index a012eb4b00093..ea9130d85ec3a 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs @@ -24,10 +24,13 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertLinq; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertLinqQueryToForEach), Shared] internal sealed class CSharpConvertLinqQueryToForEachProvider : AbstractConvertLinqQueryToForEachProvider { - private static readonly TypeSyntax VarNameIdentifier = SyntaxFactory.IdentifierName("var"); + private static readonly TypeSyntax VarNameIdentifier = IdentifierName("var"); [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] @@ -92,7 +95,7 @@ SyntaxKind.MultiLineCommentTrivia or if (!documentUpdateInfo.Source.IsParentKind(SyntaxKind.Block) && documentUpdateInfo.Destinations.Length > 1) { - documentUpdateInfo = new DocumentUpdateInfo(documentUpdateInfo.Source, SyntaxFactory.Block(documentUpdateInfo.Destinations)); + documentUpdateInfo = new DocumentUpdateInfo(documentUpdateInfo.Source, Block(documentUpdateInfo.Destinations)); } return true; @@ -113,7 +116,7 @@ private StatementSyntax ProcessClause( switch (node.Kind()) { case SyntaxKind.WhereClause: - return SyntaxFactory.Block(SyntaxFactory.IfStatement(((WhereClauseSyntax)node).Condition.WithAdditionalAnnotations(Simplifier.Annotation).WithoutTrivia(), statement)); + return Block(IfStatement(((WhereClauseSyntax)node).Condition.WithAdditionalAnnotations(Simplifier.Annotation).WithoutTrivia(), statement)); case SyntaxKind.FromClause: var fromClause = (FromClauseSyntax)node; @@ -130,14 +133,14 @@ private StatementSyntax ProcessClause( _cancellationToken); var variable = GetFreeSymbolNameAndMarkUsed(expressionName); extraStatementToAddAbove = CreateLocalDeclarationStatement(variable, fromClause.Expression, generateTypeFromExpression: false); - expression1 = SyntaxFactory.IdentifierName(variable); + expression1 = IdentifierName(variable); } else { expression1 = fromClause.Expression.WithoutTrivia(); } - return SyntaxFactory.ForEachStatement( + return ForEachStatement( fromClause.Type ?? VarNameIdentifier, fromClause.Identifier, expression1, @@ -175,7 +178,7 @@ private StatementSyntax ProcessClause( extraStatementToAddAbove = CreateLocalDeclarationStatement(variable, joinClause.InExpression, generateTypeFromExpression: false); // Replace YY() with yy declared above. - expression2 = SyntaxFactory.IdentifierName(variable); + expression2 = IdentifierName(variable); } // Output for the join @@ -186,21 +189,21 @@ private StatementSyntax ProcessClause( // if (object.Equals(x, y)) // { // ... - return SyntaxFactory.Block( - SyntaxFactory.ForEachStatement( + return Block( + ForEachStatement( joinClause.Type ?? VarNameIdentifier, joinClause.Identifier, expression2, - SyntaxFactory.Block( - SyntaxFactory.IfStatement( - SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression( + Block( + IfStatement( + InvocationExpression( + MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, - SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)), - SyntaxFactory.IdentifierName(nameof(object.Equals))), - SyntaxFactory.ArgumentList([ - SyntaxFactory.Argument(joinClause.LeftExpression), - SyntaxFactory.Argument(joinClause.RightExpression.WithoutTrailingTrivia())])), + PredefinedType(ObjectKeyword), + IdentifierName(nameof(object.Equals))), + ArgumentList([ + Argument(joinClause.LeftExpression), + Argument(joinClause.RightExpression.WithoutTrailingTrivia())])), statement)))).WithAdditionalAnnotations(Simplifier.Annotation); } case SyntaxKind.SelectClause: @@ -312,9 +315,9 @@ private bool TryConvertIfInCountInvocation( invocationExpression, queryExpressionProcessingInfo, IsInt, - (variableIdentifier, expression) => SyntaxFactory.ExpressionStatement( - SyntaxFactory.PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, variableIdentifier)), // Generating 'count++' - SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0)), // count = 0 + (variableIdentifier, expression) => ExpressionStatement( + PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, variableIdentifier)), // Generating 'count++' + LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)), // count = 0 variableName: "count", out documentUpdateInfo); } @@ -344,15 +347,15 @@ private bool TryConvertIfInToListInvocation( invocationExpression, queryExpressionProcessingInfo, IsList, - (listIdentifier, expression) => SyntaxFactory.ExpressionStatement(SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression( + (listIdentifier, expression) => ExpressionStatement(InvocationExpression( + MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, listIdentifier, - SyntaxFactory.IdentifierName(nameof(IList.Add))), - SyntaxFactory.ArgumentList([SyntaxFactory.Argument(expression)]))), - SyntaxFactory.ObjectCreationExpression( + IdentifierName(nameof(IList.Add))), + ArgumentList([Argument(expression)]))), + ObjectCreationExpression( methodSymbol.GenerateReturnTypeSyntax().WithAdditionalAnnotations(Simplifier.Annotation), - SyntaxFactory.ArgumentList(), + ArgumentList(), initializer: null), variableName: "list", out documentUpdateInfo); @@ -446,7 +449,7 @@ void Convert( // var list = new List(); or assignment // foreach(...) // IReadOnlyList a = list; - variableLocal = SyntaxFactory.IdentifierName(symbolName); + variableLocal = IdentifierName(symbolName); nodesBeforeLocal = new[] { CreateLocalDeclarationStatement(symbolName, initializer, generateTypeFromExpression: false) }; nodesAfterLocal = [parentStatement.ReplaceNode(invocationExpression, variableLocal.WithAdditionalAnnotations(Simplifier.Annotation))]; } @@ -465,7 +468,7 @@ SyntaxKind.VariableDeclaration or { var variableDeclarator = ((VariableDeclaratorSyntax)invocationParent.Parent); Convert( - SyntaxFactory.IdentifierName(variableDeclarator.Identifier), + IdentifierName(variableDeclarator.Identifier), ((VariableDeclarationSyntax)variableDeclarator.Parent).Type, checkForLocalOrParameter: false, out variable, @@ -495,9 +498,9 @@ SyntaxKind.VariableDeclaration or // after var list = new List(); // foreach(...) // return list; - variable = SyntaxFactory.IdentifierName(symbolName); + variable = IdentifierName(symbolName); nodesBefore = new[] { CreateLocalDeclarationStatement(symbolName, initializer, generateTypeFromExpression: false) }; - nodesAfter = new[] { SyntaxFactory.ReturnStatement(variable).WithAdditionalAnnotations(Simplifier.Annotation) }; + nodesAfter = new[] { ReturnStatement(variable).WithAdditionalAnnotations(Simplifier.Annotation) }; return true; // SyntaxKind.Argument: // SyntaxKind.ArrowExpressionClause is not supported @@ -518,13 +521,13 @@ private LocalDeclarationStatementSyntax CreateLocalDeclarationStatement( var typeSyntax = generateTypeFromExpression ? _semanticModel.GetTypeInfo(expression, _cancellationToken).ConvertedType.GenerateTypeSyntax() : VarNameIdentifier; - return SyntaxFactory.LocalDeclarationStatement( - SyntaxFactory.VariableDeclaration( + return LocalDeclarationStatement( + VariableDeclaration( typeSyntax, - [SyntaxFactory.VariableDeclarator( + [VariableDeclarator( identifier, argumentList: null, - SyntaxFactory.EqualsValueClause(expression))])).WithAdditionalAnnotations(Simplifier.Annotation); + EqualsValueClause(expression))])).WithAdditionalAnnotations(Simplifier.Annotation); } private bool TryReplaceWithLocalFunction(QueryExpressionProcessingInfo queryExpressionProcessingInfo, out DocumentUpdateInfo documentUpdateInfo) @@ -567,7 +570,7 @@ private bool TryReplaceWithLocalFunction(QueryExpressionProcessingInfo queryExpr } static StatementSyntax internalNodeMethod(ExpressionSyntax expression) - => SyntaxFactory.YieldStatement(SyntaxKind.YieldReturnStatement, expression); + => YieldStatement(SyntaxKind.YieldReturnStatement, expression); var statements = GenerateStatements(internalNodeMethod, queryExpressionProcessingInfo); var localFunctionNamePrefix = _semanticFacts.GenerateNameForExpression( @@ -576,23 +579,23 @@ static StatementSyntax internalNodeMethod(ExpressionSyntax expression) capitalize: false, _cancellationToken); var localFunctionToken = GetFreeSymbolNameAndMarkUsed(localFunctionNamePrefix); - var localFunctionDeclaration = SyntaxFactory.LocalFunctionStatement( + var localFunctionDeclaration = LocalFunctionStatement( modifiers: default, returnType: returnedType.GenerateTypeSyntax().WithAdditionalAnnotations(Simplifier.Annotation), identifier: localFunctionToken, typeParameterList: null, - parameterList: SyntaxFactory.ParameterList(), + parameterList: ParameterList(), constraintClauses: default, - body: SyntaxFactory.Block( - SyntaxFactory.Token( + body: Block( + Token( [], SyntaxKind.OpenBraceToken, - [SyntaxFactory.EndOfLine(Environment.NewLine)]), + [EndOfLine(Environment.NewLine)]), [.. statements], - SyntaxFactory.Token(SyntaxKind.CloseBraceToken)), + CloseBraceToken), expressionBody: null); - var localFunctionInvocation = SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName(localFunctionToken)).WithAdditionalAnnotations(Simplifier.Annotation); + var localFunctionInvocation = InvocationExpression(IdentifierName(localFunctionToken)).WithAdditionalAnnotations(Simplifier.Annotation); var newParentExpressionStatement = parentStatement.ReplaceNode(_source.WalkUpParentheses(), localFunctionInvocation.WithAdditionalAnnotations(Simplifier.Annotation)); documentUpdateInfo = new DocumentUpdateInfo(parentStatement, [localFunctionDeclaration, newParentExpressionStatement]); return true; @@ -688,13 +691,13 @@ private DocumentUpdateInfo ConvertIfInToForeachWithExtraVariableDeclaration( // dosomething(x); // } var statements = GenerateStatements( - expression => AddToBlockTop(SyntaxFactory.LocalDeclarationStatement( - SyntaxFactory.VariableDeclaration( + expression => AddToBlockTop(LocalDeclarationStatement( + VariableDeclaration( forEachStatement.Type, - [SyntaxFactory.VariableDeclarator( + [VariableDeclarator( forEachStatement.Identifier, argumentList: null, - SyntaxFactory.EqualsValueClause(expression))])), + EqualsValueClause(expression))])), forEachStatement.Statement).WithAdditionalAnnotations(Formatter.Annotation), queryExpressionProcessingInfo); return new DocumentUpdateInfo(forEachStatement, statements); } @@ -759,10 +762,10 @@ private bool TryConvertIfInReturnStatement( // // yield break; var statements = GenerateStatements((ExpressionSyntax expression) - => SyntaxFactory.YieldStatement(SyntaxKind.YieldReturnStatement, expression), queryExpressionProcessingInfo); + => YieldStatement(SyntaxKind.YieldReturnStatement, expression), queryExpressionProcessingInfo); // add an yield break to avoid throws after the return. - var yieldBreakStatement = SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement); + var yieldBreakStatement = YieldStatement(SyntaxKind.YieldBreakStatement); documentUpdateInfo = new DocumentUpdateInfo(returnStatement, statements.Concat(new[] { yieldBreakStatement })); return true; } @@ -824,7 +827,7 @@ private StatementSyntax[] GenerateStatements( // The stack was processed in the reverse order, but the extra statements should be provided in the direct order. statements.Reverse(); statements.Add(statement.WithAdditionalAnnotations(Simplifier.Annotation)); - return statements.ToArray(); + return [.. statements]; } private bool TryProcessQueryBody(QueryBodySyntax queryBody, QueryExpressionProcessingInfo queryExpressionProcessingInfo) @@ -896,7 +899,7 @@ private static BlockSyntax AddToBlockTop(StatementSyntax newStatement, Statement } else { - return SyntaxFactory.Block(newStatement, statement); + return Block(newStatement, statement); } } @@ -914,7 +917,7 @@ private static bool IsLocalOrParameterSymbol(IOperation operation) } private static BlockSyntax WrapWithBlock(StatementSyntax statement) - => statement is BlockSyntax block ? block : SyntaxFactory.Block(statement); + => statement is BlockSyntax block ? block : Block(statement); // Checks if the node is within an immediate lambda or within an immediate anonymous method. // 'lambda => node' returns true diff --git a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractConverter.cs b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractConverter.cs index 2602bf63af3ef..f8582891b488c 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractConverter.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractConverter.cs @@ -18,6 +18,9 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertLinq.ConvertForEachToLinqQuery; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal abstract class AbstractConverter(ForEachInfo forEachInfo) : IConverter { public ForEachInfo ForEachInfo { get; } = forEachInfo; @@ -54,11 +57,11 @@ private QueryExpressionSyntax CreateQueryExpression( ExpressionSyntax selectExpression, IEnumerable leadingTokensForSelect, IEnumerable trailingTokensForSelect) - => SyntaxFactory.QueryExpression( + => QueryExpression( CreateFromClause(ForEachInfo.ForEachStatement, ForEachInfo.LeadingTokens.GetTrivia(), []), - SyntaxFactory.QueryBody( + QueryBody( [.. ForEachInfo.ConvertingExtendedNodes.Select(node => CreateQueryClause(node))], - SyntaxFactory.SelectClause(selectExpression) + SelectClause(selectExpression) .WithCommentsFrom(leadingTokensForSelect, ForEachInfo.TrailingTokens.Concat(trailingTokensForSelect)), continuation: null)) // The current coverage of foreach statements to support does not need to use query continuations. .WithAdditionalAnnotations(Formatter.Annotation); @@ -69,8 +72,8 @@ private static QueryClauseSyntax CreateQueryClause(ExtendedSyntaxNode node) { case SyntaxKind.VariableDeclarator: var variable = (VariableDeclaratorSyntax)node.Node; - return SyntaxFactory.LetClause( - SyntaxFactory.Token(SyntaxKind.LetKeyword), + return LetClause( + LetKeyword, variable.Identifier, variable.Initializer.EqualsToken, variable.Initializer.Value) @@ -81,8 +84,8 @@ private static QueryClauseSyntax CreateQueryClause(ExtendedSyntaxNode node) case SyntaxKind.IfStatement: var ifStatement = (IfStatementSyntax)node.Node; - return SyntaxFactory.WhereClause( - SyntaxFactory.Token(SyntaxKind.WhereKeyword) + return WhereClause( + WhereKeyword .WithCommentsFrom(ifStatement.IfKeyword.LeadingTrivia, ifStatement.IfKeyword.TrailingTrivia), ifStatement.Condition.WithCommentsFrom(ifStatement.OpenParenToken, ifStatement.CloseParenToken)) .WithCommentsFrom(node.ExtraLeadingComments, node.ExtraTrailingComments); @@ -95,8 +98,8 @@ private static FromClauseSyntax CreateFromClause( ForEachStatementSyntax forEachStatement, IEnumerable extraLeadingTrivia, IEnumerable extraTrailingTrivia) - => SyntaxFactory.FromClause( - fromKeyword: SyntaxFactory.Token(SyntaxKind.FromKeyword) + => FromClause( + fromKeyword: FromKeyword .WithCommentsFrom( forEachStatement.ForEachKeyword.LeadingTrivia, forEachStatement.ForEachKeyword.TrailingTrivia, @@ -162,8 +165,8 @@ private ExpressionSyntax CreateLinqInvocationOrSimpleExpression( // var hasForEachChild = false; var lambdaBody = CreateLinqInvocationForExtendedNode(selectExpression, ref currentExtendedNodeIndex, ref receiverForInvocation, ref hasForEachChild); - var lambda = SyntaxFactory.SimpleLambdaExpression( - SyntaxFactory.Parameter( + var lambda = SimpleLambdaExpression( + Parameter( forEachStatement.Identifier.WithPrependedLeadingTrivia( SyntaxNodeOrTokenExtensions.GetTrivia(forEachStatement.Type.GetFirstToken()) .FilterComments(addElasticMarker: false))), @@ -201,12 +204,12 @@ lambdaBody is IdentifierNameSyntax identifier && return receiverForInvocation.WithAppendedTrailingTrivia(droppedTrivia); } - return SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression( + return InvocationExpression( + MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, receiverForInvocation.Parenthesize(), - SyntaxFactory.IdentifierName(invokedMethodName)), - SyntaxFactory.ArgumentList([SyntaxFactory.Argument(lambda)])); + IdentifierName(invokedMethodName)), + ArgumentList([Argument(lambda)])); } /// @@ -269,19 +272,19 @@ private ExpressionSyntax CreateLinqInvocationForExtendedNode( case SyntaxKind.IfStatement: var ifStatement = (IfStatementSyntax)node.Node; var parentForEachStatement = ifStatement.GetAncestor(); - var lambdaParameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier(parentForEachStatement.Identifier.ValueText)); - var lambda = SyntaxFactory.SimpleLambdaExpression( - SyntaxFactory.Parameter( - SyntaxFactory.Identifier(parentForEachStatement.Identifier.ValueText)), + var lambdaParameter = Parameter(Identifier(parentForEachStatement.Identifier.ValueText)); + var lambda = SimpleLambdaExpression( + Parameter( + Identifier(parentForEachStatement.Identifier.ValueText)), ifStatement.Condition.WithCommentsFrom(ifStatement.OpenParenToken, ifStatement.CloseParenToken)) .WithCommentsFrom(ifStatement.IfKeyword.GetAllTrivia().Concat(node.ExtraLeadingComments), node.ExtraTrailingComments); - receiver = SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression( + receiver = InvocationExpression( + MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, receiver.Parenthesize(), - SyntaxFactory.IdentifierName(nameof(Enumerable.Where))), - SyntaxFactory.ArgumentList([SyntaxFactory.Argument(lambda)])); + IdentifierName(nameof(Enumerable.Where))), + ArgumentList([Argument(lambda)])); ++extendedNodeIndex; return CreateLinqInvocationForExtendedNode(selectExpression, ref extendedNodeIndex, ref receiver, ref hasForEachChild); diff --git a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs index ea8ae8a8da756..61f9cf84c86fe 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs @@ -124,8 +124,7 @@ void Convert(ExpressionSyntax replacingExpression, SyntaxNode nodeToRemoveIfFoll // Output: // return queryGenerated.ToList(); or return queryGenerated.Count(); replacingExpression = returnStatement.Expression; - leadingTrivia = GetTriviaFromNode(nodeToRemoveIfFollowedByReturn) - .Concat(SyntaxNodeOrTokenExtensions.GetTrivia(replacingExpression)).ToArray(); + leadingTrivia = [.. GetTriviaFromNode(nodeToRemoveIfFollowedByReturn), .. SyntaxNodeOrTokenExtensions.GetTrivia(replacingExpression)]; editor.RemoveNode(nodeToRemoveIfFollowedByReturn); } else diff --git a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/DefaultConverter.cs b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/DefaultConverter.cs index 53c5142fa885b..0df51188be9a8 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/DefaultConverter.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/DefaultConverter.cs @@ -16,9 +16,11 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertLinq.ConvertForEachToLinqQuery; +using static SyntaxFactory; + internal sealed class DefaultConverter(ForEachInfo forEachInfo) : AbstractConverter(forEachInfo) { - private static readonly TypeSyntax VarNameIdentifier = SyntaxFactory.IdentifierName("var"); + private static readonly TypeSyntax VarNameIdentifier = IdentifierName("var"); public override void Convert(SyntaxEditor editor, bool convertToQuery, CancellationToken cancellationToken) { @@ -48,11 +50,11 @@ private StatementSyntax CreateDefaultReplacementStatement( if (identifiersCount == 0) { // Generate foreach(var _ ... select new {}) - return SyntaxFactory.ForEachStatement( + return ForEachStatement( VarNameIdentifier, - SyntaxFactory.Identifier("_"), + Identifier("_"), CreateQueryExpressionOrLinqInvocation( - SyntaxFactory.AnonymousObjectCreationExpression(), + AnonymousObjectCreationExpression(), [], [], convertToQuery), @@ -61,11 +63,11 @@ private StatementSyntax CreateDefaultReplacementStatement( else if (identifiersCount == 1) { // Generate foreach(var singleIdentifier from ... select singleIdentifier) - return SyntaxFactory.ForEachStatement( + return ForEachStatement( VarNameIdentifier, identifiers.Single(), CreateQueryExpressionOrLinqInvocation( - SyntaxFactory.IdentifierName(identifiers.Single()), + IdentifierName(identifiers.Single()), [], [], convertToQuery), @@ -73,16 +75,16 @@ private StatementSyntax CreateDefaultReplacementStatement( } else { - var tupleForSelectExpression = SyntaxFactory.TupleExpression( + var tupleForSelectExpression = TupleExpression( [.. identifiers.Select( - identifier => SyntaxFactory.Argument(SyntaxFactory.IdentifierName(identifier)))]); - var declaration = SyntaxFactory.DeclarationExpression( + identifier => Argument(IdentifierName(identifier)))]); + var declaration = DeclarationExpression( VarNameIdentifier, - SyntaxFactory.ParenthesizedVariableDesignation( - [.. identifiers.Select(SyntaxFactory.SingleVariableDesignation)])); + ParenthesizedVariableDesignation( + [.. identifiers.Select(SingleVariableDesignation)])); // Generate foreach(var (a,b) ... select (a, b)) - return SyntaxFactory.ForEachVariableStatement( + return ForEachVariableStatement( declaration, CreateQueryExpressionOrLinqInvocation( tupleForSelectExpression, @@ -94,5 +96,5 @@ [.. identifiers.Select( } private static BlockSyntax WrapWithBlockIfNecessary(ImmutableArray statements) - => statements is [BlockSyntax block] ? block : SyntaxFactory.Block(statements); + => statements is [BlockSyntax block] ? block : Block(statements); } diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index c355bf94dd0bb..617ca5843708c 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -29,6 +29,7 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; +using static CSharpSyntaxTokens; using static SyntaxFactory; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertPrimaryToRegularConstructor), Shared] @@ -239,7 +240,7 @@ ImmutableDictionary CreateSynthesizedFields() } } - return result.ToImmutableHashSet(); + return [.. result]; } void RemovePrimaryConstructorParameterList() @@ -470,7 +471,7 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() var constructorDeclaration = ConstructorDeclaration( [.. methodTargetingAttributes.Select(a => a.WithTarget(null).WithoutTrivia().WithAdditionalAnnotations(Formatter.Annotation))], - [Token(SyntaxKind.PublicKeyword).WithAppendedTrailingTrivia(Space)], + [PublicKeyword.WithAppendedTrailingTrivia(Space)], typeDeclaration.Identifier.WithoutTrivia(), rewrittenParameters.WithoutTrivia(), baseType?.ArgumentList is null ? null : ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, baseType.ArgumentList), diff --git a/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_ProgramMain.cs b/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_ProgramMain.cs index a9cf044833bc5..bd8b54b33a3dc 100644 --- a/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_ProgramMain.cs +++ b/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_ProgramMain.cs @@ -18,6 +18,7 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertProgram; +using static CSharpSyntaxTokens; using static SyntaxFactory; internal static partial class ConvertProgramTransform @@ -93,7 +94,7 @@ private static async Task GenerateProgramClassAsync( // Workaround for simplification not being ready when we generate a new file. Substitute System.String[] // with string[]. if (method.ParameterList.Parameters is [{ Type: ArrayTypeSyntax arrayType }]) - method = method.ReplaceNode(arrayType.ElementType, PredefinedType(Token(SyntaxKind.StringKeyword))); + method = method.ReplaceNode(arrayType.ElementType, PredefinedType(StringKeyword)); if (oldClassDeclaration is null) { @@ -145,7 +146,7 @@ private static ImmutableArray GenerateProgramMainStatements( } } - return statements.ToImmutable(); + return statements.ToImmutableAndClear(); } private static TSyntaxNode FixupComments(TSyntaxNode node) where TSyntaxNode : SyntaxNode diff --git a/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_TopLevelStatements.cs b/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_TopLevelStatements.cs index 1e313a61b1226..7cbadc2a91c8a 100644 --- a/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_TopLevelStatements.cs +++ b/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_TopLevelStatements.cs @@ -227,11 +227,11 @@ private static ImmutableArray GetGlobalStatements( } } - using var _1 = ArrayBuilder.GetInstance(out var globalStatements); + var globalStatements = new FixedSizeArrayBuilder(statements.Count); foreach (var statement in statements) globalStatements.Add(GlobalStatement(statement).WithAdditionalAnnotations(Formatter.Annotation)); - return globalStatements.ToImmutable(); + return globalStatements.MoveToImmutable(); } private static VariableDeclarationSyntax ConvertDeclaration( diff --git a/src/Features/CSharp/Portable/Copilot/CSharpCopilotCodeFixProvider.cs b/src/Features/CSharp/Portable/Copilot/CSharpCopilotCodeFixProvider.cs index 1718fea285747..f885a57f9cfe4 100644 --- a/src/Features/CSharp/Portable/Copilot/CSharpCopilotCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/Copilot/CSharpCopilotCodeFixProvider.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -57,13 +56,17 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var document = context.Document; var cancellationToken = context.CancellationToken; - var copilotService = document.GetLanguageService(); - if (copilotService is null || !await copilotService.IsCodeAnalysisOptionEnabledAsync().ConfigureAwait(false)) + if (document.GetLanguageService() is not { } copilotOptionsService || + await copilotOptionsService.IsCodeAnalysisOptionEnabledAsync().ConfigureAwait(false) is false) + { return; + } - var isAvailable = await copilotService.IsAvailableAsync(cancellationToken).ConfigureAwait(false); - if (!isAvailable) + if (document.GetLanguageService() is not { } copilotService || + await copilotService.IsAvailableAsync(cancellationToken).ConfigureAwait(false) is false) + { return; + } var promptTitles = await copilotService.GetAvailablePromptTitlesAsync(document, cancellationToken).ConfigureAwait(false); if (promptTitles.IsEmpty) @@ -71,20 +74,17 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var hasMultiplePrompts = promptTitles.Length > 1; - // Find the containing method, if any, and also update the fix span to the entire method. - // TODO: count location in doc-comment as part of the method. + // Find the containing method for each diagnostic, and register a fix if any part of the method interect with context span. var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var containingMethod = CSharpSyntaxFacts.Instance.GetContainingMethodDeclaration(root, context.Span.Start, useFullSpan: false); - if (containingMethod is not BaseMethodDeclarationSyntax) - return; - foreach (var diagnostic in context.Diagnostics) { - Debug.Assert(containingMethod.FullSpan.IntersectsWith(diagnostic.Location.SourceSpan)); - - var fix = TryGetFix(document, containingMethod, diagnostic, hasMultiplePrompts); - if (fix != null) - context.RegisterCodeFix(fix, diagnostic); + var containingMethod = CSharpSyntaxFacts.Instance.GetContainingMethodDeclaration(root, diagnostic.Location.SourceSpan.Start, useFullSpan: false); + if (containingMethod?.Span.IntersectsWith(context.Span) is true) + { + var fix = TryGetFix(document, containingMethod, diagnostic, hasMultiplePrompts); + if (fix != null) + context.RegisterCodeFix(fix, diagnostic); + } } } @@ -105,8 +105,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) // Parse the proposed Copilot fix into a method declaration. // Guard against failure cases where the proposed fixed code does not parse into a method declaration. // TODO: consider do this early when we create the diagnostic and add a flag in the property bag to speedup lightbulb computation - var fixMethodDeclaration = SyntaxFactory.ParseMemberDeclaration(fix, options: method.SyntaxTree.Options); - if (fixMethodDeclaration is null || !fixMethodDeclaration.IsKind(SyntaxKind.MethodDeclaration) || fixMethodDeclaration.GetDiagnostics().Count() > 3) + var memberDeclaration = SyntaxFactory.ParseMemberDeclaration(fix, options: method.SyntaxTree.Options); + if (memberDeclaration is null || memberDeclaration is not BaseMethodDeclarationSyntax baseMethodDeclaration || baseMethodDeclaration.GetDiagnostics().Count() > 3) return null; var title = hasMultiplePrompts @@ -125,9 +125,9 @@ async Task GetFixedDocumentAsync(SyntaxNode method, string fix, Cancel var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); // TODO: Replace all the whitespace trivia with elastic trivia, and any other trivia related improvements - var newMethod = fixMethodDeclaration - .WithLeadingTrivia(fixMethodDeclaration.HasLeadingTrivia ? fixMethodDeclaration.GetLeadingTrivia() : method.GetLeadingTrivia()) - .WithTrailingTrivia(fixMethodDeclaration.HasTrailingTrivia ? fixMethodDeclaration.GetTrailingTrivia() : method.GetTrailingTrivia()) + var newMethod = memberDeclaration + .WithLeadingTrivia(memberDeclaration.HasLeadingTrivia ? memberDeclaration.GetLeadingTrivia() : method.GetLeadingTrivia()) + .WithTrailingTrivia(memberDeclaration.HasTrailingTrivia ? memberDeclaration.GetTrailingTrivia() : method.GetTrailingTrivia()) .WithAdditionalAnnotations(Formatter.Annotation, WarningAnnotation); editor.ReplaceNode(method, newMethod); diff --git a/src/Features/CSharp/Portable/Debugging/BreakpointResolver.cs b/src/Features/CSharp/Portable/Debugging/BreakpointResolver.cs index bbe8ec72f150a..aea7caa572892 100644 --- a/src/Features/CSharp/Portable/Debugging/BreakpointResolver.cs +++ b/src/Features/CSharp/Portable/Debugging/BreakpointResolver.cs @@ -92,7 +92,7 @@ protected override void ParseText( } else { - nameParts = SpecializedCollections.EmptyList(); + nameParts = []; } } } diff --git a/src/Features/CSharp/Portable/DecompiledSource/CSharpDecompiledSourceService.cs b/src/Features/CSharp/Portable/DecompiledSource/CSharpDecompiledSourceService.cs index 9ecb17847e198..6b4a2e9bd14db 100644 --- a/src/Features/CSharp/Portable/DecompiledSource/CSharpDecompiledSourceService.cs +++ b/src/Features/CSharp/Portable/DecompiledSource/CSharpDecompiledSourceService.cs @@ -61,7 +61,7 @@ public static async Task FormatDocumentAsync(Document document, Syntax // Apply formatting rules var formattedDoc = await Formatter.FormatAsync( document, - SpecializedCollections.SingletonEnumerable(node.FullSpan), + [node.FullSpan], options, CSharpDecompiledSourceFormattingRule.Instance.Concat(Formatter.GetDefaultFormattingRules(document)), cancellationToken).ConfigureAwait(false); diff --git a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs index 8c621ef831a49..e472dfb91502e 100644 --- a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs +++ b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs @@ -24,10 +24,12 @@ namespace Microsoft.CodeAnalysis.CSharp.DocumentHighlighting; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class CSharpDocumentHighlightsService( - [ImportMany] IEnumerable> services) : AbstractDocumentHighlightsService(LanguageNames.CSharp, - CSharpEmbeddedLanguagesProvider.Info, - CSharpSyntaxKinds.Instance, - services) + [ImportMany] IEnumerable> services) + : AbstractDocumentHighlightsService( + LanguageNames.CSharp, + CSharpEmbeddedLanguagesProvider.Info, + CSharpSyntaxKinds.Instance, + services) { protected override async Task> GetAdditionalReferencesAsync( Document document, ISymbol symbol, CancellationToken cancellationToken) @@ -56,7 +58,9 @@ protected override async Task> GetAdditionalReferencesA if (type.IsVar) { - semanticModel ??= await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + // Document highlights are not impacted by nullable analysis. Get a semantic model with nullability + // disabled to lower the amount of work we need to do here. + semanticModel ??= await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); var boundSymbol = semanticModel.GetSymbolInfo(type, cancellationToken).Symbol; boundSymbol = boundSymbol?.OriginalDefinition; @@ -67,6 +71,6 @@ protected override async Task> GetAdditionalReferencesA } } - return results.ToImmutable(); + return results.ToImmutableAndClear(); } } diff --git a/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs b/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs index 5c033411ba311..cf472d6a2eb78 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/BreakpointSpans.cs @@ -472,13 +472,13 @@ internal static TextSpan CreateSpanForImplicitConstructorInitializer(Constructor => CreateSpan(constructor.Modifiers, constructor.Identifier, constructor.ParameterList.CloseParenToken); internal static IEnumerable GetActiveTokensForImplicitConstructorInitializer(ConstructorDeclarationSyntax constructor) - => constructor.Modifiers.Concat(SpecializedCollections.SingletonEnumerable(constructor.Identifier)).Concat(constructor.ParameterList.DescendantTokens()); + => constructor.Modifiers.Concat([constructor.Identifier]).Concat(constructor.ParameterList.DescendantTokens()); internal static TextSpan CreateSpanForExplicitConstructorInitializer(ConstructorInitializerSyntax constructorInitializer) => CreateSpan(constructorInitializer.ThisOrBaseKeyword, constructorInitializer.ArgumentList.CloseParenToken); internal static IEnumerable GetActiveTokensForExplicitConstructorInitializer(ConstructorInitializerSyntax constructorInitializer) - => SpecializedCollections.SingletonEnumerable(constructorInitializer.ThisOrBaseKeyword).Concat(constructorInitializer.ArgumentList.DescendantTokens()); + => [constructorInitializer.ThisOrBaseKeyword, .. constructorInitializer.ArgumentList.DescendantTokens()]; internal static TextSpan CreateSpanForImplicitPrimaryConstructorInitializer(TypeDeclarationSyntax typeDeclaration) { @@ -851,9 +851,7 @@ internal static TextSpan CreateSpanForVariableDeclarator( internal static IEnumerable GetActiveTokensForVariableDeclarator(VariableDeclaratorSyntax variableDeclarator, SyntaxTokenList modifiers, SyntaxToken semicolon) { if (variableDeclarator.Initializer == null || modifiers.Any(SyntaxKind.ConstKeyword)) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; // [|int F = 1;|] var variableDeclaration = (VariableDeclarationSyntax)variableDeclarator.Parent!; diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index b2c1e3974176b..991287eeaeeaf 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -29,18 +29,12 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue; internal sealed class CSharpEditAndContinueAnalyzer(Action? testFaultInjector = null) : AbstractEditAndContinueAnalyzer(testFaultInjector) { [ExportLanguageServiceFactory(typeof(IEditAndContinueAnalyzer), LanguageNames.CSharp), Shared] - internal sealed class Factory : ILanguageServiceFactory + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class Factory() : ILanguageServiceFactory { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public Factory() - { - } - public ILanguageService CreateLanguageService(HostLanguageServices languageServices) - { - return new CSharpEditAndContinueAnalyzer(testFaultInjector: null); - } + => new CSharpEditAndContinueAnalyzer(testFaultInjector: null); } #region Syntax Analysis @@ -927,7 +921,7 @@ private static bool AreSimilarActiveStatements(CommonForEachStatementSyntax oldN RoslynDebug.Assert(oldTokens != null); RoslynDebug.Assert(newTokens != null); - return DeclareSameIdentifiers(oldTokens.ToArray(), newTokens.ToArray()); + return DeclareSameIdentifiers([.. oldTokens], [.. newTokens]); } protected override bool AreEquivalentImpl(SyntaxToken oldToken, SyntaxToken newToken) diff --git a/src/Features/CSharp/Portable/EditAndContinue/DeclarationBody/CSharpLambdaBody.cs b/src/Features/CSharp/Portable/EditAndContinue/DeclarationBody/CSharpLambdaBody.cs index 9b8377c27cd4d..a08875d79f112 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/DeclarationBody/CSharpLambdaBody.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/DeclarationBody/CSharpLambdaBody.cs @@ -55,7 +55,7 @@ public override bool TryMatchActiveStatement(DeclarationBody newBody, SyntaxNode => LambdaUtilities.TryGetCorrespondingLambdaBody(node, newLambda) is { } newNode ? new CSharpLambdaBody(newNode) : null; public override IEnumerable GetExpressionsAndStatements() - => SpecializedCollections.SingletonEnumerable(node); + => [node]; public override SyntaxNode GetLambda() => LambdaUtilities.GetLambda(node); diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs index b3472fac53302..c8352f7280dfa 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs @@ -1003,7 +1003,7 @@ private static void GetNestedFunctionsParts( } else { - parameters = SpecializedCollections.EmptyEnumerable(); + parameters = []; } asyncKeyword = anonymous.AsyncKeyword; diff --git a/src/Features/CSharp/Portable/EncapsulateField/CSharpEncapsulateFieldService.cs b/src/Features/CSharp/Portable/EncapsulateField/CSharpEncapsulateFieldService.cs index c7be2c182fab3..f9c34dedd824e 100644 --- a/src/Features/CSharp/Portable/EncapsulateField/CSharpEncapsulateFieldService.cs +++ b/src/Features/CSharp/Portable/EncapsulateField/CSharpEncapsulateFieldService.cs @@ -25,6 +25,9 @@ namespace Microsoft.CodeAnalysis.CSharp.EncapsulateField; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportLanguageService(typeof(AbstractEncapsulateFieldService), LanguageNames.CSharp), Shared] internal class CSharpEncapsulateFieldService : AbstractEncapsulateFieldService { @@ -49,12 +52,12 @@ protected override async Task RewriteFieldNameAndAccessibilityAsync( var tempAnnotation = new SyntaxAnnotation(); var escapedName = originalFieldName.EscapeIdentifier(); - var newIdentifier = SyntaxFactory.Identifier( - leading: [SyntaxFactory.ElasticMarker], + var newIdentifier = Identifier( + leading: [ElasticMarker], contextualKind: SyntaxKind.IdentifierName, text: escapedName, valueText: originalFieldName, - trailing: [SyntaxFactory.ElasticMarker]) + trailing: [ElasticMarker]) .WithTrailingTrivia(declarator.Identifier.TrailingTrivia) .WithLeadingTrivia(declarator.Identifier.LeadingTrivia); @@ -73,11 +76,8 @@ protected override async Task RewriteFieldNameAndAccessibilityAsync( if (makePrivate) { - var modifiers = SpecializedCollections.SingletonEnumerable(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)) - .Concat(fieldSyntax.Modifiers.Where(m => !modifierKinds.Contains(m.Kind()))); - root = root.ReplaceNode(fieldSyntax, fieldSyntax - .WithModifiers([.. modifiers]) + .WithModifiers([PrivateKeyword, .. fieldSyntax.Modifiers.Where(m => !modifierKinds.Contains(m.Kind()))]) .WithAdditionalAnnotations(Formatter.Annotation) .WithLeadingTrivia(fieldSyntax.GetLeadingTrivia()) .WithTrailingTrivia(fieldSyntax.GetTrailingTrivia())); diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs index 508f03bd15ebe..b95671ab482d1 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.MultipleStatementsCodeGenerator.cs @@ -60,7 +60,7 @@ protected override ImmutableArray GetInitialStatementsForMethod } } - return list.ToImmutable(); + return list.ToImmutableAndClear(); } private static IEnumerable GetStatementsFromContainer(SyntaxNode node) diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs index eeee5ca192274..f097a8e1ad20c 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs @@ -29,6 +29,7 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; +using static CSharpSyntaxTokens; using static SyntaxFactory; internal partial class CSharpMethodExtractor @@ -178,8 +179,8 @@ private static bool IsExpressionBodiedAccessor(SyntaxNode node) private SimpleNameSyntax CreateMethodNameForInvocation() { return AnalyzerResult.MethodTypeParametersInDeclaration.Count == 0 - ? SyntaxFactory.IdentifierName(_methodName) - : SyntaxFactory.GenericName(_methodName, SyntaxFactory.TypeArgumentList(CreateMethodCallTypeVariables())); + ? IdentifierName(_methodName) + : GenericName(_methodName, TypeArgumentList(CreateMethodCallTypeVariables())); } private SeparatedSyntaxList CreateMethodCallTypeVariables() @@ -278,8 +279,8 @@ private ImmutableArray WrapInCheckStatementIfNeeded(ImmutableAr return statements; return statements is [BlockSyntax block] - ? [SyntaxFactory.CheckedStatement(kind, block)] - : [SyntaxFactory.CheckedStatement(kind, SyntaxFactory.Block(statements))]; + ? [CheckedStatement(kind, block)] + : [CheckedStatement(kind, Block(statements))]; } private static ImmutableArray CleanupCode(ImmutableArray statements) @@ -394,9 +395,9 @@ private ImmutableArray MoveDeclarationOutFromMethodDefinition( // return survived var decls if (list.Count > 0) { - result.Add(SyntaxFactory.LocalDeclarationStatement( + result.Add(LocalDeclarationStatement( declarationStatement.Modifiers, - SyntaxFactory.VariableDeclaration( + VariableDeclaration( declarationStatement.Declaration.Type, [.. list]), declarationStatement.SemicolonToken.WithPrependedLeadingTrivia(triviaList))); @@ -407,7 +408,7 @@ private ImmutableArray MoveDeclarationOutFromMethodDefinition( result.AddRange(expressionStatements); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } /// @@ -447,7 +448,7 @@ private static StatementSyntax FixDeclarationExpressionsAndDeclarationPatterns(S newLeadingTrivia = newLeadingTrivia.AddRange(declaration.Type.GetTrailingTrivia()); newLeadingTrivia = newLeadingTrivia.AddRange(designation.GetLeadingTrivia()); - replacements.Add(declaration, SyntaxFactory.IdentifierName(designation.Identifier) + replacements.Add(declaration, IdentifierName(designation.Identifier) .WithLeadingTrivia(newLeadingTrivia)); } @@ -520,9 +521,9 @@ private ImmutableArray SplitOrMoveDeclarationIntoMethodDefiniti private static ExpressionSyntax CreateAssignmentExpression(SyntaxToken identifier, ExpressionSyntax rvalue) { - return SyntaxFactory.AssignmentExpression( + return AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, - SyntaxFactory.IdentifierName(identifier), + IdentifierName(identifier), rvalue); } @@ -555,13 +556,13 @@ protected override bool LastStatementOrHasReturnStatementInReturnableConstruct() } protected override SyntaxToken CreateIdentifier(string name) - => SyntaxFactory.Identifier(name); + => Identifier(name); protected override StatementSyntax CreateReturnStatement(string identifierName = null) { return string.IsNullOrEmpty(identifierName) - ? SyntaxFactory.ReturnStatement() - : SyntaxFactory.ReturnStatement(SyntaxFactory.IdentifierName(identifierName)); + ? ReturnStatement() + : ReturnStatement(IdentifierName(identifierName)); } protected override ExpressionSyntax CreateCallSignature() @@ -611,7 +612,7 @@ protected override ExpressionSyntax CreateCallSignature() } protected override StatementSyntax CreateAssignmentExpressionStatement(SyntaxToken identifier, ExpressionSyntax rvalue) - => SyntaxFactory.ExpressionStatement(CreateAssignmentExpression(identifier, rvalue)); + => ExpressionStatement(CreateAssignmentExpression(identifier, rvalue)); protected override StatementSyntax CreateDeclarationStatement( VariableInfo variable, @@ -626,14 +627,14 @@ protected override StatementSyntax CreateDeclarationStatement( // Hierarchy being checked for to see if a using keyword is needed is // Token -> VariableDeclarator -> VariableDeclaration -> LocalDeclaration var usingKeyword = originalIdentifierToken.Parent?.Parent?.Parent is LocalDeclarationStatementSyntax { UsingKeyword.FullSpan.IsEmpty: false } - ? SyntaxFactory.Token(SyntaxKind.UsingKeyword) + ? UsingKeyword : default; - var equalsValueClause = initialValue == null ? null : SyntaxFactory.EqualsValueClause(value: initialValue); + var equalsValueClause = initialValue == null ? null : EqualsValueClause(value: initialValue); - return SyntaxFactory.LocalDeclarationStatement( - SyntaxFactory.VariableDeclaration(typeNode) - .AddVariables(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(variable.Name)) + return LocalDeclarationStatement( + VariableDeclaration(typeNode) + .AddVariables(VariableDeclarator(Identifier(variable.Name)) .WithInitializer(equalsValueClause))) .WithUsingKeyword(usingKeyword); } @@ -673,14 +674,14 @@ private static TDeclarationNode TweakNewLinesInMethod(TDeclara return method.ReplaceToken( body.OpenBraceToken, body.OpenBraceToken.WithAppendedTrailingTrivia( - SpecializedCollections.SingletonEnumerable(SyntaxFactory.ElasticCarriageReturnLineFeed))); + ElasticCarriageReturnLineFeed)); } else if (expressionBody != null) { return method.ReplaceToken( expressionBody.ArrowToken, expressionBody.ArrowToken.WithPrependedLeadingTrivia( - SpecializedCollections.SingletonEnumerable(SyntaxFactory.ElasticCarriageReturnLineFeed))); + ElasticCarriageReturnLineFeed)); } else { @@ -695,10 +696,10 @@ protected StatementSyntax GetStatementContainingInvocationToExtractedMethodWorke if (AnalyzerResult.HasReturnType) { Contract.ThrowIfTrue(AnalyzerResult.HasVariableToUseAsReturnValue); - return SyntaxFactory.ReturnStatement(callSignature); + return ReturnStatement(callSignature); } - return SyntaxFactory.ExpressionStatement(callSignature); + return ExpressionStatement(callSignature); } protected override async Task UpdateMethodAfterGenerationAsync( @@ -794,7 +795,7 @@ protected SyntaxToken GenerateMethodNameForStatementGenerators() scope = this.SelectionResult.GetFirstTokenInSelection().Parent; } - return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(scope, GenerateMethodNameFromUserPreference())); + return Identifier(nameGenerator.CreateUniqueMethodName(scope, GenerateMethodNameFromUserPreference())); } protected string GenerateMethodNameFromUserPreference() diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs index 51c77bc339f61..5b0dfa7a74172 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs @@ -14,6 +14,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod; +using static SyntaxFactory; + internal partial class CSharpMethodExtractor { private class PostProcessor @@ -106,7 +108,7 @@ private ImmutableArray MergeDeclarationStatementsWorker(Immutab if (map.Count > 0) result.AddRange(GetMergedDeclarationStatements(map)); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private void AppendDeclarationStatementToMap( @@ -141,8 +143,8 @@ private static IEnumerable GetMergedDeclaration // and create one decl statement // use type name from the first decl statement yield return - SyntaxFactory.LocalDeclarationStatement( - SyntaxFactory.VariableDeclaration(keyValuePair.Value.First().Declaration.Type, [.. variables])); + LocalDeclarationStatement( + VariableDeclaration(keyValuePair.Value.First().Declaration.Type, [.. variables])); } map.Clear(); @@ -256,7 +258,7 @@ declaration.Declaration.Variables[0].Initializer.Value is StackAllocArrayCreatio return statements; } - return [SyntaxFactory.ReturnStatement(declaration.Declaration.Variables[0].Initializer.Value)]; + return [ReturnStatement(declaration.Declaration.Variables[0].Initializer.Value)]; } public static ImmutableArray RemoveDeclarationAssignmentPattern(ImmutableArray statements) @@ -291,7 +293,7 @@ public static ImmutableArray RemoveDeclarationAssignmentPattern return statements; } - var variable = declaration.Declaration.Variables[0].WithInitializer(SyntaxFactory.EqualsValueClause(assignmentExpression.Right)); + var variable = declaration.Declaration.Variables[0].WithInitializer(EqualsValueClause(assignmentExpression.Right)); return [ declaration.WithDeclaration( diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.TriviaResult.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.TriviaResult.cs index 0d85ae254fd28..1408b5122f9d0 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.TriviaResult.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.TriviaResult.cs @@ -102,9 +102,7 @@ private IEnumerable TriviaResolver( if (tokenPair.PreviousToken == body.OpenBraceToken && tokenPair.NextToken == body.CloseBraceToken) { - return (location == TriviaLocation.AfterBeginningOfSpan) - ? SpecializedCollections.SingletonEnumerable(SyntaxFactory.ElasticMarker) - : SpecializedCollections.EmptyEnumerable(); + return location == TriviaLocation.AfterBeginningOfSpan ? [SyntaxFactory.ElasticMarker] : []; } } else @@ -112,17 +110,15 @@ private IEnumerable TriviaResolver( if (tokenPair.PreviousToken == expressionBody.ArrowToken && tokenPair.NextToken.GetPreviousToken() == semicolonToken) { - return (location == TriviaLocation.AfterBeginningOfSpan) - ? SpecializedCollections.SingletonEnumerable(SyntaxFactory.ElasticMarker) - : SpecializedCollections.EmptyEnumerable(); + return location == TriviaLocation.AfterBeginningOfSpan ? [SyntaxFactory.ElasticMarker] : []; } } triviaMap.TryGetValue(tokenPair.PreviousToken, out var previousTriviaPair); - var trailingTrivia = previousTriviaPair.TrailingTrivia ?? SpecializedCollections.EmptyEnumerable(); + var trailingTrivia = previousTriviaPair.TrailingTrivia ?? []; triviaMap.TryGetValue(tokenPair.NextToken, out var nextTriviaPair); - var leadingTrivia = nextTriviaPair.LeadingTrivia ?? SpecializedCollections.EmptyEnumerable(); + var leadingTrivia = nextTriviaPair.LeadingTrivia ?? []; var list = trailingTrivia.Concat(leadingTrivia); @@ -166,7 +162,7 @@ private static IEnumerable AppendLeadingTrivia(PreviousNextTokenPa return tokenPair.NextToken.LeadingTrivia; } - return SpecializedCollections.EmptyEnumerable(); + return []; } private static IEnumerable AppendTrailingTrivia(PreviousNextTokenPair tokenPair) @@ -177,7 +173,7 @@ private static IEnumerable AppendTrailingTrivia(PreviousNextTokenP return tokenPair.PreviousToken.TrailingTrivia; } - return SpecializedCollections.EmptyEnumerable(); + return []; } } } diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs index 0db1910e9e56d..3c7586b532fca 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs @@ -181,12 +181,12 @@ protected override SyntaxNode ParseTypeName(string name) { var originalMethodDefinition = methodDefinition; var newLine = Options.LineFormattingOptions.NewLine; - methodDefinition = methodDefinition.WithPrependedLeadingTrivia(SpecializedCollections.SingletonEnumerable(SyntaxFactory.EndOfLine(newLine))); + methodDefinition = methodDefinition.WithPrependedLeadingTrivia(SyntaxFactory.EndOfLine(newLine)); if (!originalMethodDefinition.FindTokenOnLeftOfPosition(originalMethodDefinition.SpanStart).TrailingTrivia.Any(SyntaxKind.EndOfLineTrivia)) { // Add a second new line since there were no line endings in the original form - methodDefinition = methodDefinition.WithPrependedLeadingTrivia(SpecializedCollections.SingletonEnumerable(SyntaxFactory.EndOfLine(newLine))); + methodDefinition = methodDefinition.WithPrependedLeadingTrivia(SyntaxFactory.EndOfLine(newLine)); } // Generating the new document and associated variables. diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.cs index c27ebc93ca693..1e0195d297e03 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionValidator.cs @@ -290,7 +290,7 @@ private static bool CheckTopLevel(SyntaxNode node, TextSpan span) break; } - case GlobalStatementSyntax _: + case GlobalStatementSyntax: return true; case ConstructorInitializerSyntax constructorInitializer: return constructorInitializer.ContainsInArgument(span); @@ -453,9 +453,7 @@ public override IEnumerable GetOuterReturnStatements(SyntaxNode comm var container = commonRoot.GetAncestorsOrThis().Where(a => a.IsReturnableConstruct()).FirstOrDefault(); if (container == null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var returnableConstructPairs = returnStatements.Select(r => (r, r.GetAncestors().Where(a => a.IsReturnableConstruct()).FirstOrDefault())) .Where(p => p.Item2 != null); diff --git a/src/Features/CSharp/Portable/FullyQualify/CSharpFullyQualifyService.cs b/src/Features/CSharp/Portable/FullyQualify/CSharpFullyQualifyService.cs index 4002b668e5fba..80fddc03c80d7 100644 --- a/src/Features/CSharp/Portable/FullyQualify/CSharpFullyQualifyService.cs +++ b/src/Features/CSharp/Portable/FullyQualify/CSharpFullyQualifyService.cs @@ -14,6 +14,8 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.FullyQualify; +using static CSharpSyntaxTokens; + [ExportLanguageService(typeof(IFullyQualifyService), LanguageNames.CSharp), Shared] internal sealed class CSharpFullyQualifyService : AbstractFullyQualifyService { @@ -57,7 +59,7 @@ protected override async Task ReplaceNodeAsync(SimpleNameSyntax simp simpleName.Parent is UsingDirectiveSyntax { Alias: null, StaticKeyword.RawKind: 0 } usingDirective) { var newUsingDirective = usingDirective - .WithStaticKeyword(SyntaxFactory.Token(SyntaxKind.StaticKeyword)) + .WithStaticKeyword(StaticKeyword) .WithName(qualifiedName); return root.ReplaceNode(usingDirective, newUsingDirective); diff --git a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs index 06b21c73514f7..3a5e3e9a9d8dd 100644 --- a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs +++ b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs @@ -59,7 +59,7 @@ protected override ImmutableArray GetCapturedTypeParameter type.GetReferencedTypeParameters(result); } - return result.ToImmutableArray(); + return [.. result]; } protected override ImmutableArray GenerateTypeParameters(CancellationToken cancellationToken) @@ -84,9 +84,10 @@ protected override ImmutableArray GenerateTypeParameters(C } else { - using var _ = ArrayBuilder.GetInstance(out var list); + var list = new FixedSizeArrayBuilder(genericName.TypeArgumentList.Arguments.Count); - var usedIdentifiers = new HashSet { "T" }; + using var _ = PooledHashSet.GetInstance(out var usedIdentifiers); + usedIdentifiers.Add("T"); foreach (var type in genericName.TypeArgumentList.Arguments) { var typeParameter = GetUniqueTypeParameter( @@ -99,7 +100,7 @@ protected override ImmutableArray GenerateTypeParameters(C list.Add(typeParameter); } - return list.ToImmutable(); + return list.MoveToImmutable(); } } @@ -147,18 +148,18 @@ protected override bool IsImplicitReferenceConversion(Compilation compilation, I protected override ImmutableArray DetermineTypeArguments(CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var result); - if (State.SimpleNameOpt is GenericNameSyntax genericName) + if (State.SimpleNameOpt is not GenericNameSyntax genericName) + return []; + + var result = new FixedSizeArrayBuilder(genericName.TypeArgumentList.Arguments.Count); + foreach (var typeArgument in genericName.TypeArgumentList.Arguments) { - foreach (var typeArgument in genericName.TypeArgumentList.Arguments) - { - var typeInfo = Document.SemanticModel.GetTypeInfo(typeArgument, cancellationToken); - result.Add(typeInfo.Type); - } + var typeInfo = Document.SemanticModel.GetTypeInfo(typeArgument, cancellationToken); + result.Add(typeInfo.Type); } - return result.ToImmutable(); + return result.MoveToImmutable(); } } } diff --git a/src/Features/CSharp/Portable/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs b/src/Features/CSharp/Portable/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs index dff654471beb5..d5edd33fdae68 100644 --- a/src/Features/CSharp/Portable/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs +++ b/src/Features/CSharp/Portable/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs @@ -18,6 +18,8 @@ namespace Microsoft.CodeAnalysis.CSharp.GenerateMember.GenerateVariable; +using static SyntaxFactory; + [ExportLanguageService(typeof(IGenerateVariableService), LanguageNames.CSharp), Shared] internal partial class CSharpGenerateVariableService : AbstractGenerateVariableService @@ -151,7 +153,7 @@ private static bool IsProbablyGeneric(SimpleNameSyntax identifierName, Cancellat var localText = localRoot.ToString(); var startIndex = identifierName.Span.Start - localRoot.Span.Start; - var parsedType = SyntaxFactory.ParseTypeName(localText, startIndex, consumeFullText: false); + var parsedType = ParseTypeName(localText, startIndex, consumeFullText: false); return parsedType.IsKind(SyntaxKind.GenericName) && !parsedType.ContainsDiagnostics; } @@ -202,10 +204,10 @@ protected override bool TryConvertToLocalDeclaration(ITypeSymbol type, SyntaxTok var assignExpression = (AssignmentExpressionSyntax)node.Parent; var expressionStatement = (StatementSyntax)assignExpression.Parent; - var declarationStatement = SyntaxFactory.LocalDeclarationStatement( - SyntaxFactory.VariableDeclaration( + var declarationStatement = LocalDeclarationStatement( + VariableDeclaration( type.GenerateTypeSyntax(), - [SyntaxFactory.VariableDeclarator(token, null, SyntaxFactory.EqualsValueClause( + [VariableDeclarator(token, null, EqualsValueClause( assignExpression.OperatorToken, assignExpression.Right))])); declarationStatement = declarationStatement.WithAdditionalAnnotations(Formatter.Annotation); diff --git a/src/Features/CSharp/Portable/Highlighting/KeywordHighlighters/AsyncAwaitHighlighter.cs b/src/Features/CSharp/Portable/Highlighting/KeywordHighlighters/AsyncAwaitHighlighter.cs index ced8b66e42e30..01f4dd450e1fa 100644 --- a/src/Features/CSharp/Portable/Highlighting/KeywordHighlighters/AsyncAwaitHighlighter.cs +++ b/src/Features/CSharp/Portable/Highlighting/KeywordHighlighters/AsyncAwaitHighlighter.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Highlighting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.KeywordHighlighting.KeywordHighlighters; @@ -49,9 +50,8 @@ private static IEnumerable WalkChildren(SyntaxNode node) var stack = pooledObject.Object; stack.Push(node); - while (stack.Count > 0) + while (stack.TryPop(out var current)) { - var current = stack.Pop(); yield return current; // 'Reverse' isn't really necessary, but it means we walk the nodes in document diff --git a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs index 2bb6df09a4620..5e8e826c29438 100644 --- a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs +++ b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs @@ -59,7 +59,7 @@ SyntaxKind.RecordDeclaration or { classOrStructDecl = interfaceNode.Parent.Parent.Parent as TypeDeclarationSyntax; classOrStructType = model.GetDeclaredSymbol(classOrStructDecl, cancellationToken) as INamedTypeSymbol; - interfaceTypes = SpecializedCollections.SingletonEnumerable(interfaceType); + interfaceTypes = [interfaceType]; return interfaceTypes != null && classOrStructType != null; } diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs index 08955f457e2d3..6f111100fc423 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs @@ -14,6 +14,8 @@ namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter; +using static CSharpSyntaxTokens; + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.AddParameterCheck), Shared] [ExtensionOrder(Before = PredefinedCodeRefactoringProviderNames.ChangeSignature)] internal sealed class CSharpAddParameterCheckCodeRefactoringProvider : @@ -48,7 +50,7 @@ protected override bool CanOffer(SyntaxNode body) if (InitializeParameterHelpers.IsExpressionBody(body)) { return InitializeParameterHelpers.TryConvertExpressionBodyToStatement(body, - semicolonToken: Token(SyntaxKind.SemicolonToken), + semicolonToken: SemicolonToken, createReturnStatementForExpression: false, statement: out var _); } @@ -66,7 +68,7 @@ protected override StatementSyntax CreateParameterCheckIfStatement(ExpressionSyn { var withBlock = options.PreferBraces.Value == CodeAnalysis.CodeStyle.PreferBracesPreference.Always; var singleLine = options.AllowEmbeddedStatementsOnSameLine.Value; - var closeParenToken = Token(SyntaxKind.CloseParenToken); + var closeParenToken = CloseParenToken; if (withBlock) { ifTrueStatement = Block(ifTrueStatement); @@ -82,8 +84,8 @@ protected override StatementSyntax CreateParameterCheckIfStatement(ExpressionSyn return IfStatement( attributeLists: default, - ifKeyword: Token(SyntaxKind.IfKeyword), - openParenToken: Token(SyntaxKind.OpenParenToken), + ifKeyword: IfKeyword, + openParenToken: OpenParenToken, condition: condition, closeParenToken: closeParenToken, statement: ifTrueStatement, diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index e89e8e8b4d870..1218900b9e64c 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -18,6 +18,7 @@ namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter; +using static CSharpSyntaxTokens; using static InitializeParameterHelpers; using static InitializeParameterHelpersCore; using static SyntaxFactory; @@ -234,7 +235,7 @@ await UpdateParameterReferencesAsync( editor.ReplaceNode( propertyDeclaration, newPropertyDeclaration.WithoutTrailingTrivia() - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(newPropertyDeclaration.GetTrailingTrivia())) + .WithSemicolonToken(SemicolonToken.WithTrailingTrivia(newPropertyDeclaration.GetTrailingTrivia())) .WithInitializer(initializer)); break; } diff --git a/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs b/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs index c107631da5f62..eeaff6bfc8f3e 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs @@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.InitializeParameter; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; @@ -20,6 +19,8 @@ namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter; +using static CSharpSyntaxTokens; + internal static class InitializeParameterHelpers { public static bool IsFunctionDeclaration(SyntaxNode node) @@ -64,7 +65,7 @@ public static void InsertStatement( if (IsExpressionBody(body)) { - var semicolonToken = TryGetSemicolonToken(functionDeclaration) ?? SyntaxFactory.Token(SyntaxKind.SemicolonToken); + var semicolonToken = TryGetSemicolonToken(functionDeclaration) ?? SemicolonToken; if (!TryConvertExpressionBodyToStatement(body, semicolonToken, !returnsVoid, out var convertedStatement)) { @@ -168,7 +169,7 @@ public static PropertyDeclarationSyntax RemoveThrowNotImplemented(PropertyDeclar .WithSemicolonToken(default) .AddAccessorListAccessors(SyntaxFactory .AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))) + .WithSemicolonToken(SemicolonToken)) .WithTrailingTrivia(propertyDeclaration.SemicolonToken.TrailingTrivia) .WithAdditionalAnnotations(Formatter.Annotation); return result; @@ -189,7 +190,7 @@ private static AccessorDeclarationSyntax RemoveThrowNotImplemented(AccessorDecla var result = accessorDeclaration .WithExpressionBody(null) .WithBody(null) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + .WithSemicolonToken(SemicolonToken); return result.WithTrailingTrivia(accessorDeclaration.Body?.GetTrailingTrivia() ?? accessorDeclaration.SemicolonToken.TrailingTrivia); } diff --git a/src/Features/CSharp/Portable/IntroduceUsingStatement/CSharpIntroduceUsingStatementCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/IntroduceUsingStatement/CSharpIntroduceUsingStatementCodeRefactoringProvider.cs index 251d67e7eee03..7c55cbf873ac2 100644 --- a/src/Features/CSharp/Portable/IntroduceUsingStatement/CSharpIntroduceUsingStatementCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/IntroduceUsingStatement/CSharpIntroduceUsingStatementCodeRefactoringProvider.cs @@ -13,6 +13,9 @@ namespace Microsoft.CodeAnalysis.CSharp.IntroduceUsingStatement; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExtensionOrder(Before = PredefinedCodeRefactoringProviderNames.IntroduceVariable)] [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.IntroduceUsingStatement), Shared] internal sealed class CSharpIntroduceUsingStatementCodeRefactoringProvider @@ -52,13 +55,13 @@ protected override SyntaxNode WithStatements(SyntaxNode parentOfStatementsToSurr } protected override StatementSyntax CreateUsingStatement(LocalDeclarationStatementSyntax declarationStatement, SyntaxList statementsToSurround) - => SyntaxFactory.UsingStatement( - SyntaxFactory.Token(SyntaxKind.UsingKeyword).WithLeadingTrivia(declarationStatement.GetLeadingTrivia()), - SyntaxFactory.Token(SyntaxKind.OpenParenToken), + => UsingStatement( + UsingKeyword.WithLeadingTrivia(declarationStatement.GetLeadingTrivia()), + OpenParenToken, declaration: declarationStatement.Declaration.WithoutTrivia(), expression: null, // Declaration already has equals token and expression - SyntaxFactory.Token(SyntaxKind.CloseParenToken).WithTrailingTrivia(declarationStatement.GetTrailingTrivia()), - statement: SyntaxFactory.Block(statementsToSurround)); + CloseParenToken.WithTrailingTrivia(declarationStatement.GetTrailingTrivia()), + statement: Block(statementsToSurround)); protected override bool TryCreateUsingLocalDeclaration( ParseOptions options, @@ -76,7 +79,7 @@ protected override bool TryCreateUsingLocalDeclaration( usingDeclarationStatement = declarationStatement .WithoutLeadingTrivia() - .WithUsingKeyword(SyntaxFactory.Token(declarationStatement.GetLeadingTrivia(), SyntaxKind.UsingKeyword, [SyntaxFactory.Space])); + .WithUsingKeyword(Token(declarationStatement.GetLeadingTrivia(), SyntaxKind.UsingKeyword, [Space])); return true; } } diff --git a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceLocalForExpressionCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceLocalForExpressionCodeRefactoringProvider.cs index b4217ef88fb91..a94c7015693e5 100644 --- a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceLocalForExpressionCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceLocalForExpressionCodeRefactoringProvider.cs @@ -16,13 +16,13 @@ using Microsoft.CodeAnalysis.IntroduceVariable; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.IntroduceVariable; +using static CSharpSyntaxTokens; using static SyntaxFactory; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.IntroduceLocalForExpression), Shared] @@ -64,7 +64,7 @@ protected override LocalDeclarationStatementSyntax FixupLocalDeclaration( { var expression = expressionStatement.Expression; localDeclaration = localDeclaration.ReplaceNode(value, expression.WithoutLeadingTrivia()); - semicolonToken = Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(expression.GetTrailingTrivia()); + semicolonToken = SemicolonToken.WithTrailingTrivia(expression.GetTrailingTrivia()); } return localDeclaration.WithSemicolonToken(semicolonToken); @@ -80,7 +80,7 @@ protected override ExpressionStatementSyntax FixupDeconstruction( { var expression = expressionStatement.Expression; deconstruction = deconstruction.ReplaceNode(binary.Right, expression.WithoutLeadingTrivia()); - semicolonToken = Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(expression.GetTrailingTrivia()); + semicolonToken = SemicolonToken.WithTrailingTrivia(expression.GetTrailingTrivia()); } return deconstruction.WithSemicolonToken(semicolonToken); diff --git a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceField.cs b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceField.cs index 16794661e4dda..b6e8ff9217e88 100644 --- a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceField.cs +++ b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceField.cs @@ -16,6 +16,9 @@ namespace Microsoft.CodeAnalysis.CSharp.IntroduceVariable; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal partial class CSharpIntroduceVariableService { protected override Task IntroduceFieldAsync( @@ -34,20 +37,20 @@ protected override Task IntroduceFieldAsync( var typeDisplayString = oldType.ToMinimalDisplayString(document.SemanticModel, expression.SpanStart); var newQualifiedName = oldTypeDeclaration != null - ? SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ParseName(typeDisplayString), SyntaxFactory.IdentifierName(newNameToken)) - : (ExpressionSyntax)SyntaxFactory.IdentifierName(newNameToken); + ? MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ParseName(typeDisplayString), IdentifierName(newNameToken)) + : (ExpressionSyntax)IdentifierName(newNameToken); newQualifiedName = newQualifiedName.WithAdditionalAnnotations(Simplifier.Annotation); - var newFieldDeclaration = SyntaxFactory.FieldDeclaration( + var newFieldDeclaration = FieldDeclaration( default, MakeFieldModifiers(isConstant, inScript: oldType.IsScriptClass), - SyntaxFactory.VariableDeclaration( + VariableDeclaration( GetTypeSymbol(document, expression, cancellationToken).GenerateTypeSyntax(), - [SyntaxFactory.VariableDeclarator( + [VariableDeclarator( newNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), null, - SyntaxFactory.EqualsValueClause(expression.WithoutTrivia()))])).WithAdditionalAnnotations(Formatter.Annotation); + EqualsValueClause(expression.WithoutTrivia()))])).WithAdditionalAnnotations(Formatter.Annotation); if (oldTypeDeclaration != null) { @@ -161,7 +164,7 @@ protected static int DetermineFirstChange(SyntaxList ol { for (var i = 0; i < oldMembers.Count; i++) { - if (!SyntaxFactory.AreEquivalent(oldMembers[i], newMembers[i], topLevel: false)) + if (!AreEquivalent(oldMembers[i], newMembers[i], topLevel: false)) { return i; } @@ -183,15 +186,15 @@ private static SyntaxTokenList MakeFieldModifiers(bool isConstant, bool inScript { if (isConstant) { - return [SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ConstKeyword)]; + return [PrivateKeyword, ConstKeyword]; } else if (inScript) { - return [SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)]; + return [PrivateKeyword, ReadOnlyKeyword]; } else { - return [SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)]; + return [PrivateKeyword, StaticKeyword, ReadOnlyKeyword]; } } } diff --git a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs index ba1b5efdbcf11..6ae16bb46eed9 100644 --- a/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs +++ b/src/Features/CSharp/Portable/IntroduceVariable/CSharpIntroduceVariableService_IntroduceLocal.cs @@ -20,6 +20,9 @@ namespace Microsoft.CodeAnalysis.CSharp.IntroduceVariable; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal partial class CSharpIntroduceVariableService { protected override async Task IntroduceLocalAsync( @@ -34,20 +37,20 @@ protected override async Task IntroduceLocalAsync( var newLocalNameToken = GenerateUniqueLocalName( document, expression, isConstant, containerToGenerateInto, cancellationToken); - var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); + var newLocalName = IdentifierName(newLocalNameToken); var modifiers = isConstant - ? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ConstKeyword)) + ? TokenList(ConstKeyword) : default; - var declarationStatement = SyntaxFactory.LocalDeclarationStatement( + var declarationStatement = LocalDeclarationStatement( modifiers, - SyntaxFactory.VariableDeclaration( + VariableDeclaration( GetTypeSyntax(document, expression, cancellationToken), - [SyntaxFactory.VariableDeclarator( + [VariableDeclarator( newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), null, - SyntaxFactory.EqualsValueClause(expression.WithoutTrivia()))])); + EqualsValueClause(expression.WithoutTrivia()))])); // If we're inserting into a multi-line parent, then add a newline after the local-var // we're adding. That way we don't end up having it and the starting statement be on @@ -55,7 +58,7 @@ protected override async Task IntroduceLocalAsync( var text = await document.Document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); if (!text.AreOnSameLine(containerToGenerateInto.GetFirstToken(), containerToGenerateInto.GetLastToken())) { - declarationStatement = declarationStatement.WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed); + declarationStatement = declarationStatement.WithAppendedTrailingTrivia(ElasticCarriageReturnLineFeed); } switch (containerToGenerateInto) @@ -106,7 +109,7 @@ private Document IntroduceLocalDeclarationIntoLambda( declarationStatement, isEntireLambdaBodySelected, rewrittenBody, shouldIncludeReturnStatement); // Add an elastic newline so that the formatter will place this new lambda body across multiple lines. - newBody = newBody.WithOpenBraceToken(newBody.OpenBraceToken.WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed)) + newBody = newBody.WithOpenBraceToken(newBody.OpenBraceToken.WithAppendedTrailingTrivia(ElasticCarriageReturnLineFeed)) .WithAdditionalAnnotations(Formatter.Annotation); var newLambda = oldLambda.WithBody(newBody); @@ -171,7 +174,7 @@ private static BlockSyntax GetNewBlockBodyForLambda( // var v = x + 1; // return v; // }; - return SyntaxFactory.Block(declarationStatement, SyntaxFactory.ReturnStatement(rewrittenBody)); + return Block(declarationStatement, ReturnStatement(rewrittenBody)); } // For lambdas with void return types, we don't need to include the rewritten body if the entire lambda body @@ -188,7 +191,7 @@ private static BlockSyntax GetNewBlockBodyForLambda( // { // string v = x.ToString(); // }; - return SyntaxFactory.Block(declarationStatement); + return Block(declarationStatement); } // Case 2b: The lambda has a void return type, and the user didn't select the entire lambda body. @@ -201,9 +204,9 @@ private static BlockSyntax GetNewBlockBodyForLambda( // string destFileName = Path.Combine("dir", "file"); // File.Copy("src", destFileName); // }); - return SyntaxFactory.Block( + return Block( declarationStatement, - SyntaxFactory.ExpressionStatement(rewrittenBody, SyntaxFactory.Token(SyntaxKind.SemicolonToken))); + ExpressionStatement(rewrittenBody, SemicolonToken)); } private static TypeSyntax GetTypeSyntax(SemanticDocument document, ExpressionSyntax expression, CancellationToken cancellationToken) @@ -230,15 +233,15 @@ private Document RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( var newExpression = Rewrite(document, expression, newLocalName, document, oldBody.Expression, allOccurrences, cancellationToken); var convertedStatement = createReturnStatement - ? SyntaxFactory.ReturnStatement(newExpression) - : (StatementSyntax)SyntaxFactory.ExpressionStatement(newExpression); + ? ReturnStatement(newExpression) + : (StatementSyntax)ExpressionStatement(newExpression); - var newBody = SyntaxFactory.Block(declarationStatement, convertedStatement) + var newBody = Block(declarationStatement, convertedStatement) .WithLeadingTrivia(leadingTrivia) .WithTrailingTrivia(oldBody.GetTrailingTrivia()); // Add an elastic newline so that the formatter will place this new block across multiple lines. - newBody = newBody.WithOpenBraceToken(newBody.OpenBraceToken.WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed)) + newBody = newBody.WithOpenBraceToken(newBody.OpenBraceToken.WithAppendedTrailingTrivia(ElasticCarriageReturnLineFeed)) .WithAdditionalAnnotations(Formatter.Annotation); var newRoot = document.Root.ReplaceNode(oldParentingNode, WithBlockBody(oldParentingNode, newBody)); @@ -250,30 +253,30 @@ private static SyntaxNode WithBlockBody(SyntaxNode node, BlockSyntax body) switch (node) { case BasePropertyDeclarationSyntax baseProperty: - var accessorList = SyntaxFactory.AccessorList( - [SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, body)]); + var accessorList = AccessorList( + [AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, body)]); return baseProperty .TryWithExpressionBody(null) .WithAccessorList(accessorList) - .TryWithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .TryWithSemicolonToken(Token(SyntaxKind.None)) .WithTriviaFrom(baseProperty); case AccessorDeclarationSyntax accessor: return accessor .WithExpressionBody(null) .WithBody(body) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithSemicolonToken(Token(SyntaxKind.None)) .WithTriviaFrom(accessor); case BaseMethodDeclarationSyntax baseMethod: return baseMethod .WithExpressionBody(null) .WithBody(body) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithSemicolonToken(Token(SyntaxKind.None)) .WithTriviaFrom(baseMethod); case LocalFunctionStatementSyntax localFunction: return localFunction .WithExpressionBody(null) .WithBody(body) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithSemicolonToken(Token(SyntaxKind.None)) .WithTriviaFrom(localFunction); default: throw ExceptionUtilities.UnexpectedValue(node); @@ -326,7 +329,7 @@ private async Task IntroduceLocalDeclarationIntoBlockAsync( { root = root.TrackNodes(allAffectedStatements.Concat(new SyntaxNode[] { expression, statement })); root = root.ReplaceNode(root.GetCurrentNode(statement), - SyntaxFactory.Block(root.GetCurrentNode(statement)).WithAdditionalAnnotations(Formatter.Annotation)); + Block(root.GetCurrentNode(statement)).WithAdditionalAnnotations(Formatter.Annotation)); expression = root.GetCurrentNode(expression); allAffectedStatements = allAffectedStatements.Select(root.GetCurrentNode).ToSet(); diff --git a/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs index 125273e608a2a..3daf3339608c8 100644 --- a/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs @@ -18,6 +18,8 @@ namespace Microsoft.CodeAnalysis.CSharp.InvertIf; +using static SyntaxFactory; + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InvertIf), Shared] internal sealed class CSharpInvertIfCodeRefactoringProvider : AbstractInvertIfCodeRefactoringProvider< SyntaxKind, StatementSyntax, IfStatementSyntax, StatementSyntax> @@ -59,7 +61,7 @@ protected override StatementSyntax GetIfBody(IfStatementSyntax ifNode) => ifNode.Statement; protected override StatementSyntax GetEmptyEmbeddedStatement() - => SyntaxFactory.Block(); + => Block(); protected override StatementSyntax GetElseBody(IfStatementSyntax ifNode) => ifNode.Else?.Statement ?? throw new InvalidOperationException(); @@ -108,10 +110,10 @@ CommonForEachStatementSyntax or DoStatementSyntax or WhileStatementSyntax or For protected override StatementSyntax GetJumpStatement(SyntaxKind kind) => kind switch { - SyntaxKind.ContinueStatement => SyntaxFactory.ContinueStatement(), - SyntaxKind.BreakStatement => SyntaxFactory.BreakStatement(), - SyntaxKind.ReturnStatement => SyntaxFactory.ReturnStatement(), - SyntaxKind.YieldBreakStatement => SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement), + SyntaxKind.ContinueStatement => ContinueStatement(), + SyntaxKind.BreakStatement => BreakStatement(), + SyntaxKind.ReturnStatement => ReturnStatement(), + SyntaxKind.YieldBreakStatement => YieldStatement(SyntaxKind.YieldBreakStatement), _ => throw ExceptionUtilities.UnexpectedValue(kind), }; @@ -127,7 +129,7 @@ protected override StatementSyntax AsEmbeddedStatement(IEnumerable GetNormalAnonymousTypeParts members.AddRange(Space()); members.Add(Punctuation(SyntaxFacts.GetText(SyntaxKind.CloseBraceToken))); - return members.ToImmutable(); + return members.ToImmutableAndClear(); } } diff --git a/src/Features/CSharp/Portable/LineSeparators/CSharpLineSeparatorService.cs b/src/Features/CSharp/Portable/LineSeparators/CSharpLineSeparatorService.cs index 75f87a9720437..90fad59bac2ba 100644 --- a/src/Features/CSharp/Portable/LineSeparators/CSharpLineSeparatorService.cs +++ b/src/Features/CSharp/Portable/LineSeparators/CSharpLineSeparatorService.cs @@ -64,7 +64,7 @@ public async Task> GetLineSeparatorsAsync( } } - return spans.ToImmutable(); + return spans.ToImmutableAndClear(); } /// Node types that are interesting for line separation. @@ -299,7 +299,7 @@ private static void ProcessNodeList(SyntaxList children, ArrayBuilder AddAssemblyInfoRegionAsync(Document docu var assemblyInfo = MetadataAsSourceHelpers.GetAssemblyInfo(symbol.ContainingAssembly); var assemblyPath = MetadataAsSourceHelpers.GetAssemblyDisplay(symbolCompilation, symbol.ContainingAssembly); - var regionTrivia = SyntaxFactory.RegionDirectiveTrivia(true) - .WithTrailingTrivia(new[] { SyntaxFactory.Space, SyntaxFactory.PreprocessingMessage(assemblyInfo) }); + var regionTrivia = RegionDirectiveTrivia(true) + .WithTrailingTrivia(new[] { Space, PreprocessingMessage(assemblyInfo) }); var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = oldRoot.WithPrependedLeadingTrivia( - SyntaxFactory.Trivia(regionTrivia), - SyntaxFactory.CarriageReturnLineFeed, - SyntaxFactory.Comment("// " + assemblyPath), - SyntaxFactory.CarriageReturnLineFeed, - SyntaxFactory.Trivia(SyntaxFactory.EndRegionDirectiveTrivia(true)), - SyntaxFactory.CarriageReturnLineFeed, - SyntaxFactory.CarriageReturnLineFeed); + Trivia(regionTrivia), + CarriageReturnLineFeed, + Comment("// " + assemblyPath), + CarriageReturnLineFeed, + Trivia(EndRegionDirectiveTrivia(true)), + CarriageReturnLineFeed, + CarriageReturnLineFeed); return document.WithSyntaxRoot(newRoot); } @@ -140,9 +142,9 @@ private static SyntaxTrivia[] CreateNullableTrivia(bool enable) var keyword = enable ? SyntaxKind.EnableKeyword : SyntaxKind.DisableKeyword; return [ - SyntaxFactory.Trivia(SyntaxFactory.NullableDirectiveTrivia(SyntaxFactory.Token(keyword), isActive: enable)), - SyntaxFactory.ElasticCarriageReturnLineFeed, - SyntaxFactory.ElasticCarriageReturnLineFeed, + Trivia(NullableDirectiveTrivia(Token(keyword), isActive: enable)), + ElasticCarriageReturnLineFeed, + ElasticCarriageReturnLineFeed, ]; } diff --git a/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs b/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs index f08ad9316eb71..d0bc4e043f363 100644 --- a/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs +++ b/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs @@ -108,7 +108,7 @@ private static ImmutableArray GetMembersInTypes( } items.Sort((x1, x2) => x1.Text.CompareTo(x2.Text)); - return items.ToImmutable(); + return items.ToImmutableAndClear(); } } @@ -124,18 +124,15 @@ private static IEnumerable GetTypesInFile(SemanticModel semant using (Logger.LogBlock(FunctionId.NavigationBar_ItemService_GetTypesInFile_CSharp, cancellationToken)) { var types = new HashSet(); - var nodesToVisit = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var nodesToVisit); nodesToVisit.Push(semanticModel.SyntaxTree.GetRoot(cancellationToken)); - while (!nodesToVisit.IsEmpty()) + while (nodesToVisit.TryPop(out var node)) { if (cancellationToken.IsCancellationRequested) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; - var node = nodesToVisit.Pop(); var type = GetType(semanticModel, node, cancellationToken); if (type != null) diff --git a/src/Features/CSharp/Portable/Organizing/Organizers/ModifiersOrganizer.cs b/src/Features/CSharp/Portable/Organizing/Organizers/ModifiersOrganizer.cs index d4cec0353fc69..743e7b08a9f4e 100644 --- a/src/Features/CSharp/Portable/Organizing/Organizers/ModifiersOrganizer.cs +++ b/src/Features/CSharp/Portable/Organizing/Organizers/ModifiersOrganizer.cs @@ -20,14 +20,14 @@ public static SyntaxTokenList Organize(SyntaxTokenList modifiers) { var initialList = new List(modifiers); var leadingTrivia = initialList.First().LeadingTrivia; - initialList[0] = initialList[0].WithLeadingTrivia(SpecializedCollections.EmptyEnumerable()); + initialList[0] = initialList[0].WithLeadingTrivia(); var finalList = initialList.OrderBy(new Comparer()).ToList(); if (!initialList.SequenceEqual(finalList)) { finalList[0] = finalList[0].WithLeadingTrivia(leadingTrivia); - return finalList.ToSyntaxTokenList(); + return [.. finalList]; } } diff --git a/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs b/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs index 2c06e936fc06a..3e439cc5f6b4e 100644 --- a/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs +++ b/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs @@ -156,15 +156,15 @@ private static QuickInfoItem CreateQuickInfo(TextSpan location, DiagnosticDescri var idTag = !string.IsNullOrWhiteSpace(descriptor.HelpLinkUri) ? new TaggedText(TextTags.Text, descriptor.Id, TaggedTextStyle.None, descriptor.HelpLinkUri, descriptor.HelpLinkUri) : new TaggedText(TextTags.Text, descriptor.Id); - return QuickInfoItem.Create(location, sections: new[] - { - QuickInfoSection.Create(QuickInfoSectionKinds.Description, new[] - { + return QuickInfoItem.Create(location, sections: + [ + QuickInfoSection.Create(QuickInfoSectionKinds.Description, + [ idTag, new TaggedText(TextTags.Punctuation, ":"), new TaggedText(TextTags.Space, " "), new TaggedText(TextTags.Text, description) - }.ToImmutableArray()) - }.ToImmutableArray(), relatedSpans: relatedSpans.ToImmutableArray()); + ]) + ], relatedSpans: [.. relatedSpans]); } } diff --git a/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs b/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs index 275923aec259f..d91544be24d58 100644 --- a/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs +++ b/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs @@ -3,13 +3,20 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.QuickInfo; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.QuickInfo; @@ -103,11 +110,11 @@ protected override NullableFlowState GetNullabilityAnalysis(SemanticModel semant case ILocalSymbol { HasConstantValue: true }: return default; // Symbols with useful quick info - case IFieldSymbol _: - case ILocalSymbol _: - case IParameterSymbol _: - case IPropertySymbol _: - case IRangeVariableSymbol _: + case IFieldSymbol: + case ILocalSymbol: + case IParameterSymbol: + case IPropertySymbol: + case IRangeVariableSymbol: break; default: @@ -127,4 +134,46 @@ protected override NullableFlowState GetNullabilityAnalysis(SemanticModel semant return typeInfo.Nullability.FlowState; } + + protected override async Task GetOnTheFlyDocsElementAsync(QuickInfoContext context, CancellationToken cancellationToken) + { + var document = context.Document; + var position = context.Position; + + if (document.GetLanguageService() is not { } copilotService || + !await copilotService.IsAvailableAsync(cancellationToken).ConfigureAwait(false)) + { + return null; + } + + if (document.GetLanguageService() is not { } service || + !await service.IsOnTheFlyDocsOptionEnabledAsync().ConfigureAwait(false)) + { + return null; + } + + var symbolService = document.GetRequiredLanguageService(); + var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( + document, position, cancellationToken).ConfigureAwait(false); + + if (symbol is null) + { + return null; + } + + if (symbol.DeclaringSyntaxReferences.Length == 0) + { + return null; + } + + var maxLength = 1000; + var symbolStrings = symbol.DeclaringSyntaxReferences.Select(reference => + { + var span = reference.Span; + var sourceText = reference.SyntaxTree.GetText(cancellationToken); + return sourceText.GetSubText(new Text.TextSpan(span.Start, Math.Min(maxLength, span.Length))).ToString(); + }).ToImmutableArray(); + + return new OnTheFlyDocsElement(symbol.ToDisplayString(), symbolStrings, symbol.Language); + } } diff --git a/src/Features/CSharp/Portable/ReplaceMethodWithProperty/CSharpReplaceMethodWithPropertyService.cs b/src/Features/CSharp/Portable/ReplaceMethodWithProperty/CSharpReplaceMethodWithPropertyService.cs index 72a1f9d82fd06..590c1ec98e9d6 100644 --- a/src/Features/CSharp/Portable/ReplaceMethodWithProperty/CSharpReplaceMethodWithPropertyService.cs +++ b/src/Features/CSharp/Portable/ReplaceMethodWithProperty/CSharpReplaceMethodWithPropertyService.cs @@ -21,6 +21,9 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.ReplaceMethodWithProperty; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportLanguageService(typeof(IReplaceMethodWithPropertyService), LanguageNames.CSharp), Shared] internal class CSharpReplaceMethodWithPropertyService : AbstractReplaceMethodWithPropertyService, IReplaceMethodWithPropertyService { @@ -96,10 +99,10 @@ public static SyntaxNode ConvertMethodsToProperty( block: out var block)) { var accessor = - SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithBody(block); - var accessorList = SyntaxFactory.AccessorList([accessor]); + var accessorList = AccessorList([accessor]); return propertyDeclaration.WithAccessorList(accessorList) .WithExpressionBody(null) .WithSemicolonToken(default); @@ -126,7 +129,7 @@ public static PropertyDeclarationSyntax ConvertMethodsToPropertyWorker( nameToken = nameToken.WithAdditionalAnnotations(WarningAnnotation.Create(warning)); } - var property = SyntaxFactory.PropertyDeclaration( + var property = PropertyDeclaration( getMethodDeclaration.AttributeLists, getMethodDeclaration.Modifiers, getMethodDeclaration.ReturnType, getMethodDeclaration.ExplicitInterfaceSpecifier, nameToken, accessorList: null); @@ -135,13 +138,13 @@ public static PropertyDeclarationSyntax ConvertMethodsToPropertyWorker( if (setMethodDeclaration?.Modifiers.Any(SyntaxKind.UnsafeKeyword) == true && !property.Modifiers.Any(SyntaxKind.UnsafeKeyword)) { - property = property.AddModifiers(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + property = property.AddModifiers(UnsafeKeyword); } property = SetLeadingTrivia( CSharpSyntaxFacts.Instance, getAndSetMethods, property); - var accessorList = SyntaxFactory.AccessorList([getAccessor]); + var accessorList = AccessorList([getAccessor]); if (setAccessor != null) { accessorList = accessorList.AddAccessors(setAccessor); @@ -155,7 +158,7 @@ public static PropertyDeclarationSyntax ConvertMethodsToPropertyWorker( private static SyntaxToken GetPropertyName(SyntaxToken identifier, string propertyName, bool nameChanged) { return nameChanged - ? SyntaxFactory.Identifier(propertyName) + ? Identifier(propertyName) : identifier; } @@ -206,7 +209,7 @@ private static AccessorDeclarationSyntax CreateGetAccessorWorker(GetAndSetMethod { var getMethodDeclaration = getAndSetMethods.GetMethodDeclaration as MethodDeclarationSyntax; - var accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration); + var accessor = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration); if (getMethodDeclaration.ExpressionBody != null) { @@ -245,7 +248,7 @@ private static AccessorDeclarationSyntax CreateSetAccessorWorker( } var getMethod = getAndSetMethods.GetMethod; - var accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration); + var accessor = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration); if (getMethod.DeclaredAccessibility != setMethod.DeclaredAccessibility) { @@ -292,7 +295,7 @@ public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) { if (_parameter.Equals(_semanticModel.GetSymbolInfo(node).Symbol)) { - return SyntaxFactory.IdentifierName("value").WithTriviaFrom(node); + return IdentifierName("value").WithTriviaFrom(node); } return node; @@ -336,7 +339,7 @@ public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) // But also add a simplification annotation so we can remove the parens if possible. var argumentExpression = currentInvocation.ArgumentList.Arguments[0].Expression.Parenthesize(); - var expression = SyntaxFactory.AssignmentExpression( + var expression = AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, currentInvocation.Expression, argumentExpression); return expression.Parenthesize(); @@ -367,7 +370,7 @@ public static void ReplaceInvocation(SyntaxEditor editor, SyntaxToken nameToken, var newName = nameNode; if (nameChanged) { - newName = SyntaxFactory.IdentifierName(SyntaxFactory.Identifier(propertyName)); + newName = IdentifierName(Identifier(propertyName)); } newName = newName.WithTriviaFrom(invocation is null ? nameToken.Parent : invocation); diff --git a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs index 1316a5b5038c2..e74409937af67 100644 --- a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs +++ b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs @@ -16,13 +16,16 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.ReplacePropertyWithMethods; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.ReplacePropertyWithMethods; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportLanguageService(typeof(IReplacePropertyWithMethodsService), LanguageNames.CSharp), Shared] internal partial class CSharpReplacePropertyWithMethodsService : AbstractReplacePropertyWithMethodsService @@ -69,7 +72,7 @@ private static ImmutableArray ConvertPropertyToMembers( string desiredSetMethodName, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var result); + using var result = TemporaryArray.Empty; if (propertyBackingField != null) { @@ -97,7 +100,7 @@ private static ImmutableArray ConvertPropertyToMembers( cancellationToken)); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static SyntaxNode GetSetMethod( @@ -129,7 +132,7 @@ MethodDeclarationSyntax GetSetMethodWorker() if (propertyDeclaration.Modifiers.Any(SyntaxKind.UnsafeKeyword) && !methodDeclaration.Modifiers.Any(SyntaxKind.UnsafeKeyword)) { - methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + methodDeclaration = methodDeclaration.AddModifiers(UnsafeKeyword); } methodDeclaration = methodDeclaration.WithAttributeLists(setAccessorDeclaration.AttributeLists); @@ -147,7 +150,7 @@ MethodDeclarationSyntax GetSetMethodWorker() } else if (propertyBackingField != null) { - return methodDeclaration.WithBody(SyntaxFactory.Block( + return methodDeclaration.WithBody(Block( (StatementSyntax)generator.ExpressionStatement( generator.AssignmentStatement( GetFieldReference(generator, propertyBackingField), @@ -184,7 +187,7 @@ MethodDeclarationSyntax GetGetMethodWorker() if (propertyDeclaration.Modifiers.Any(SyntaxKind.UnsafeKeyword) && !methodDeclaration.Modifiers.Any(SyntaxKind.UnsafeKeyword)) { - methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + methodDeclaration = methodDeclaration.AddModifiers(UnsafeKeyword); } if (propertyDeclaration.ExpressionBody != null) @@ -214,7 +217,7 @@ MethodDeclarationSyntax GetGetMethodWorker() { var fieldReference = GetFieldReference(generator, propertyBackingField); return methodDeclaration.WithBody( - SyntaxFactory.Block( + Block( (StatementSyntax)generator.ReturnStatement(fieldReference))); } } @@ -248,7 +251,7 @@ private static SyntaxTrivia ConvertDocumentationComment(SyntaxTrivia trivia, CSh var structure = trivia.GetStructure(); var rewritten = rewriter.Visit(structure); Contract.ThrowIfNull(rewritten); - return SyntaxFactory.Trivia((StructuredTriviaSyntax)rewritten); + return Trivia((StructuredTriviaSyntax)rewritten); } private static SyntaxNode UseExpressionOrBlockBodyIfDesired( @@ -306,17 +309,17 @@ protected override NameMemberCrefSyntax CreateCrefSyntax(NameMemberCrefSyntax or CrefParameterListSyntax parameterList; if (parameterType is TypeSyntax typeSyntax) { - var parameter = SyntaxFactory.CrefParameter(typeSyntax); - parameterList = SyntaxFactory.CrefParameterList([parameter]); + var parameter = CrefParameter(typeSyntax); + parameterList = CrefParameterList([parameter]); } else { - parameterList = SyntaxFactory.CrefParameterList(); + parameterList = CrefParameterList(); } // XmlCrefAttribute replaces with {T}, which is required for C# documentation comments - var crefAttribute = SyntaxFactory.XmlCrefAttribute( - SyntaxFactory.NameMemberCref(SyntaxFactory.IdentifierName(identifierToken), parameterList)); + var crefAttribute = XmlCrefAttribute( + NameMemberCref(IdentifierName(identifierToken), parameterList)); return (NameMemberCrefSyntax)crefAttribute.Cref; } @@ -345,6 +348,6 @@ protected override ExpressionSyntax UnwrapCompoundAssignment( if (operatorKind is SyntaxKind.None) return parent; - return SyntaxFactory.BinaryExpression(operatorKind, readExpression, parent.Right.Parenthesize()); + return BinaryExpression(operatorKind, readExpression, parent.Right.Parenthesize()); } } diff --git a/src/Features/CSharp/Portable/ReverseForStatement/CSharpReverseForStatementCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ReverseForStatement/CSharpReverseForStatementCodeRefactoringProvider.cs index 2da8982c62456..72870319d2bf2 100644 --- a/src/Features/CSharp/Portable/ReverseForStatement/CSharpReverseForStatementCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ReverseForStatement/CSharpReverseForStatementCodeRefactoringProvider.cs @@ -19,7 +19,9 @@ namespace Microsoft.CodeAnalysis.CSharp.ReverseForStatement; +using static CSharpSyntaxTokens; using static IntegerUtilities; +using static SyntaxFactory; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ReverseForStatement), Shared] internal class CSharpReverseForStatementCodeRefactoringProvider : CodeRefactoringProvider @@ -334,7 +336,7 @@ reducedRight is BinaryExpressionSyntax innerRight && innerRight.Kind() == SyntaxKind.SubtractExpression && IsLiteralOne(innerRight.Right)) { - var newOperator = SyntaxFactory.Token(SyntaxKind.LessThanToken).WithTriviaFrom(outerBinary.OperatorToken); + var newOperator = LessThanToken.WithTriviaFrom(outerBinary.OperatorToken); return Reduce(outerBinary.WithRight(innerRight.Left) .WithOperatorToken(newOperator)); } @@ -344,7 +346,7 @@ reducedLeft is BinaryExpressionSyntax innerLeft && innerLeft.Kind() == SyntaxKind.SubtractExpression && IsLiteralOne(innerLeft.Right)) { - var newOperator = SyntaxFactory.Token(SyntaxKind.GreaterThanToken).WithTriviaFrom(outerBinary.OperatorToken); + var newOperator = GreaterThanToken.WithTriviaFrom(outerBinary.OperatorToken); return Reduce(outerBinary.WithRight(innerLeft.Left) .WithOperatorToken(newOperator)); } @@ -369,8 +371,8 @@ private static BinaryExpressionSyntax Invert( ? SyntaxKind.GreaterThanOrEqualExpression : SyntaxKind.LessThanOrEqualExpression; - var newOperator = SyntaxFactory.Token(newOperatorKind).WithTriviaFrom(condition.OperatorToken); - return SyntaxFactory.BinaryExpression(newExpressionKind, left, newOperator, right); + var newOperator = Token(newOperatorKind).WithTriviaFrom(condition.OperatorToken); + return BinaryExpression(newExpressionKind, left, newOperator, right); } private static ExpressionSyntax InvertAfter(ExpressionSyntax after) @@ -392,7 +394,7 @@ private static ExpressionSyntax InvertAfter(ExpressionSyntax after) _ => throw ExceptionUtilities.UnexpectedValue(opToken.Kind()) }; - var newOpToken = SyntaxFactory.Token(newKind).WithTriviaFrom(opToken); + var newOpToken = Token(newKind).WithTriviaFrom(opToken); return after.ReplaceToken(opToken, newOpToken); } } diff --git a/src/Features/CSharp/Portable/SemanticSearch/CSharpSemanticSearchService.cs b/src/Features/CSharp/Portable/SemanticSearch/CSharpSemanticSearchService.cs new file mode 100644 index 0000000000000..f4573b8e00525 --- /dev/null +++ b/src/Features/CSharp/Portable/SemanticSearch/CSharpSemanticSearchService.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. + +#if NET6_0_OR_GREATER + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.SemanticSearch.CSharp; + +[ExportLanguageService(typeof(ISemanticSearchService), LanguageNames.CSharp), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpSemanticSearchService() : AbstractSemanticSearchService() +{ + protected override Compilation CreateCompilation( + SourceText query, + IEnumerable references, + SolutionServices services, + out SyntaxTree queryTree, + CancellationToken cancellationToken) + { + var syntaxTreeFactory = services.GetRequiredLanguageService(LanguageNames.CSharp); + + var globalUsingsTree = syntaxTreeFactory.ParseSyntaxTree( + filePath: null, + CSharpSemanticSearchUtilities.ParseOptions, + SemanticSearchUtilities.CreateSourceText(CSharpSemanticSearchUtilities.Configuration.GlobalUsings), + cancellationToken); + + queryTree = syntaxTreeFactory.ParseSyntaxTree( + filePath: SemanticSearchUtilities.QueryDocumentName, + CSharpSemanticSearchUtilities.ParseOptions, + query, + cancellationToken); + + return CSharpCompilation.Create( + assemblyName: SemanticSearchUtilities.QueryProjectName, + [queryTree, globalUsingsTree], + references, + CSharpSemanticSearchUtilities.CompilationOptions); + } +} + +#endif diff --git a/src/Features/CSharp/Portable/SemanticSearch/CSharpSemanticSearchUtilities.cs b/src/Features/CSharp/Portable/SemanticSearch/CSharpSemanticSearchUtilities.cs new file mode 100644 index 0000000000000..77c6da34af215 --- /dev/null +++ b/src/Features/CSharp/Portable/SemanticSearch/CSharpSemanticSearchUtilities.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 Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal sealed class CSharpSemanticSearchUtilities +{ + public static readonly CSharpParseOptions ParseOptions = CSharpParseOptions.Default; + public static readonly CSharpCompilationOptions CompilationOptions = new(OutputKind.ConsoleApplication); + + public static readonly SemanticSearchProjectConfiguration Configuration = new() + { + Language = LanguageNames.CSharp, + Query = """ + static IEnumerable Find(Compilation compilation) + { + return compilation.GlobalNamespace.GetMembers("C"); + } + """, + GlobalUsings = """ + global using System; + global using System.Collections.Generic; + global using System.Collections.Immutable; + global using System.Linq; + global using System.Threading; + global using System.Threading.Tasks; + global using Microsoft.CodeAnalysis; + """, + EditorConfig = """ + is_global = true + + dotnet_analyzer_diagnostic.category-Documentation.severity = none + dotnet_analyzer_diagnostic.category-Globalization.severity = none + dotnet_analyzer_diagnostic.category-Interoperability.severity = none + dotnet_analyzer_diagnostic.category-Design.severity = none + dotnet_analyzer_diagnostic.category-Naming.severity = none + dotnet_analyzer_diagnostic.category-Maintainability.severity = none + dotnet_analyzer_diagnostic.category-Style.severity = none + + # CS8321: unused local function + dotnet_diagnostic.CS8321.severity = none + + # IDE051: private member is unused + dotnet_diagnostic.IDE051.severity = none + """, + ParseOptions = ParseOptions, + CompilationOptions = CompilationOptions + }; +} diff --git a/src/Features/CSharp/Portable/SignatureHelp/AbstractCSharpSignatureHelpProvider.cs b/src/Features/CSharp/Portable/SignatureHelp/AbstractCSharpSignatureHelpProvider.cs index 0c96f4a475229..363d0a1cf5e42 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/AbstractCSharpSignatureHelpProvider.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/AbstractCSharpSignatureHelpProvider.cs @@ -77,5 +77,5 @@ protected static SignatureHelpSymbolParameter Convert( #pragma warning disable CA1822 // Mark members as static - see obsolete message above. protected IList GetAwaitableUsage(IMethodSymbol method, SemanticModel semanticModel, int position) #pragma warning restore CA1822 // Mark members as static - => SpecializedCollections.EmptyList(); + => []; } diff --git a/src/Features/CSharp/Portable/SignatureHelp/AbstractOrdinaryMethodSignatureHelpProvider.cs b/src/Features/CSharp/Portable/SignatureHelp/AbstractOrdinaryMethodSignatureHelpProvider.cs index e1ac88896801d..3149432cb7d3e 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/AbstractOrdinaryMethodSignatureHelpProvider.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/AbstractOrdinaryMethodSignatureHelpProvider.cs @@ -86,5 +86,5 @@ private static IList GetMethodGroupPreambleParts( } private static IList GetMethodGroupPostambleParts() - => SpecializedCollections.SingletonList(Punctuation(SyntaxKind.CloseParenToken)); + => [Punctuation(SyntaxKind.CloseParenToken)]; } diff --git a/src/Features/CSharp/Portable/SignatureHelp/AttributeSignatureHelpProvider.cs b/src/Features/CSharp/Portable/SignatureHelp/AttributeSignatureHelpProvider.cs index ceac2f1a40932..cbae266b5700f 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/AttributeSignatureHelpProvider.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/AttributeSignatureHelpProvider.cs @@ -217,17 +217,9 @@ private static IList GetPreambleParts( SemanticModel semanticModel, int position) { - var result = new List(); - - result.AddRange(method.ContainingType.ToMinimalDisplayParts(semanticModel, position)); - result.Add(Punctuation(SyntaxKind.OpenParenToken)); - - return result; + return [.. method.ContainingType.ToMinimalDisplayParts(semanticModel, position), Punctuation(SyntaxKind.OpenParenToken)]; } private static IList GetPostambleParts() - { - return SpecializedCollections.SingletonList( - Punctuation(SyntaxKind.CloseParenToken)); - } + => [Punctuation(SyntaxKind.CloseParenToken)]; } diff --git a/src/Features/CSharp/Portable/SignatureHelp/ConstructorInitializerSignatureHelpProvider.cs b/src/Features/CSharp/Portable/SignatureHelp/ConstructorInitializerSignatureHelpProvider.cs index 6e3da4f3cb31c..db1b912ad47e0 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/ConstructorInitializerSignatureHelpProvider.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/ConstructorInitializerSignatureHelpProvider.cs @@ -155,17 +155,9 @@ private static IList GetPreambleParts( SemanticModel semanticModel, int position) { - var result = new List(); - - result.AddRange(method.ContainingType.ToMinimalDisplayParts(semanticModel, position)); - result.Add(Punctuation(SyntaxKind.OpenParenToken)); - - return result; + return [.. method.ContainingType.ToMinimalDisplayParts(semanticModel, position), Punctuation(SyntaxKind.OpenParenToken)]; } private static IList GetPostambleParts() - { - return SpecializedCollections.SingletonList( - Punctuation(SyntaxKind.CloseParenToken)); - } + => [Punctuation(SyntaxKind.CloseParenToken)]; } diff --git a/src/Features/CSharp/Portable/SignatureHelp/ElementAccessExpressionSignatureHelpProvider.cs b/src/Features/CSharp/Portable/SignatureHelp/ElementAccessExpressionSignatureHelpProvider.cs index 9bfd87398bf72..0aa0ec4939dc5 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/ElementAccessExpressionSignatureHelpProvider.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/ElementAccessExpressionSignatureHelpProvider.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.DocumentationComments; @@ -24,6 +23,8 @@ namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp; +using static SyntaxFactory; + [ExportSignatureHelpProvider("ElementAccessExpressionSignatureHelpProvider", LanguageNames.CSharp), Shared] internal sealed class ElementAccessExpressionSignatureHelpProvider : AbstractCSharpSignatureHelpProvider { @@ -153,12 +154,12 @@ private static TextSpan GetTextSpan(ExpressionSyntax expression, SyntaxToken ope // and then we need to account for this and offset the position check accordingly. int offset; BracketedArgumentListSyntax argumentList; - var newBracketedArgumentList = SyntaxFactory.ParseBracketedArgumentList(openBracket.Parent!.ToString()); + var newBracketedArgumentList = ParseBracketedArgumentList(openBracket.Parent!.ToString()); if (expression.Parent is ConditionalAccessExpressionSyntax) { // The typed code looks like: ?[ - var elementBinding = SyntaxFactory.ElementBindingExpression(newBracketedArgumentList); - var conditionalAccessExpression = SyntaxFactory.ConditionalAccessExpression(expression, elementBinding); + var elementBinding = ElementBindingExpression(newBracketedArgumentList); + var conditionalAccessExpression = ConditionalAccessExpression(expression, elementBinding); offset = expression.SpanStart - conditionalAccessExpression.SpanStart; argumentList = ((ElementBindingExpressionSyntax)conditionalAccessExpression.WhenNotNull).ArgumentList; } @@ -168,7 +169,7 @@ private static TextSpan GetTextSpan(ExpressionSyntax expression, SyntaxToken ope // [ // or // ?[ - var elementAccessExpression = SyntaxFactory.ElementAccessExpression(expression, newBracketedArgumentList); + var elementAccessExpression = ElementAccessExpression(expression, newBracketedArgumentList); offset = expression.SpanStart - elementAccessExpression.SpanStart; argumentList = elementAccessExpression.ArgumentList; } @@ -276,10 +277,7 @@ private static IList GetPreambleParts( } private static IList GetPostambleParts() - { - return SpecializedCollections.SingletonList( - Punctuation(SyntaxKind.CloseBracketToken)); - } + => [Punctuation(SyntaxKind.CloseBracketToken)]; private static class CompleteElementAccessExpression { diff --git a/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider_NamedType.cs b/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider_NamedType.cs index 95db63b5b30c9..d3546f93092f4 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider_NamedType.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider_NamedType.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp; @@ -14,17 +13,9 @@ private static IList GetPreambleParts( SemanticModel semanticModel, int position) { - var result = new List(); - - result.AddRange(namedType.ToMinimalDisplayParts(semanticModel, position, MinimallyQualifiedWithoutTypeParametersFormat)); - result.Add(Punctuation(SyntaxKind.LessThanToken)); - - return result; + return [.. namedType.ToMinimalDisplayParts(semanticModel, position, MinimallyQualifiedWithoutTypeParametersFormat), Punctuation(SyntaxKind.LessThanToken)]; } private static IList GetPostambleParts() - { - return SpecializedCollections.SingletonList( - Punctuation(SyntaxKind.GreaterThanToken)); - } + => [Punctuation(SyntaxKind.GreaterThanToken)]; } diff --git a/src/Features/CSharp/Portable/SignatureHelp/InvocationExpressionSignatureHelpProviderBase_DelegateAndFunctionPointerInvoke.cs b/src/Features/CSharp/Portable/SignatureHelp/InvocationExpressionSignatureHelpProviderBase_DelegateAndFunctionPointerInvoke.cs index faef82d3e0eb4..07a9c809660fc 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/InvocationExpressionSignatureHelpProviderBase_DelegateAndFunctionPointerInvoke.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/InvocationExpressionSignatureHelpProviderBase_DelegateAndFunctionPointerInvoke.cs @@ -53,7 +53,7 @@ private static IList GetDelegateOrFunctionPointerInvokeItems( // Since we're returning a single item, we can selected it as the "best one". selectedItem = 0; - return SpecializedCollections.SingletonList(item); + return [item]; } private static IList GetDelegateOrFunctionPointerInvokePreambleParts(IMethodSymbol invokeMethod, SemanticModel semanticModel, int position) @@ -96,8 +96,5 @@ private static IList GetDelegateOrFunctionPointerI } private static IList GetDelegateOrFunctionPointerInvokePostambleParts() - { - return SpecializedCollections.SingletonList( - Punctuation(SyntaxKind.CloseParenToken)); - } + => [Punctuation(SyntaxKind.CloseParenToken)]; } diff --git a/src/Features/CSharp/Portable/SignatureHelp/ObjectCreationExpressionSignatureHelpProvider_DelegateType.cs b/src/Features/CSharp/Portable/SignatureHelp/ObjectCreationExpressionSignatureHelpProvider_DelegateType.cs index 8edc55b68a139..c2a05bd5c24ea 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/ObjectCreationExpressionSignatureHelpProvider_DelegateType.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/ObjectCreationExpressionSignatureHelpProvider_DelegateType.cs @@ -70,17 +70,13 @@ private static IList GetDelegateTypeParameters(IMe parts.Add(Space()); parts.Add(new SymbolDisplayPart(SymbolDisplayPartKind.ParameterName, null, TargetName)); - return SpecializedCollections.SingletonList( - new SignatureHelpSymbolParameter( - TargetName, - isOptional: false, - documentationFactory: null, - displayParts: parts)); + return [new SignatureHelpSymbolParameter( + TargetName, + isOptional: false, + documentationFactory: null, + displayParts: parts)]; } private static IList GetDelegateTypePostambleParts() - { - return SpecializedCollections.SingletonList( - Punctuation(SyntaxKind.CloseParenToken)); - } + => [Punctuation(SyntaxKind.CloseParenToken)]; } diff --git a/src/Features/CSharp/Portable/SignatureHelp/ObjectCreationExpressionSignatureHelpProvider_NormalType.cs b/src/Features/CSharp/Portable/SignatureHelp/ObjectCreationExpressionSignatureHelpProvider_NormalType.cs index a966e3ea8ac79..69f3fb51b7bfe 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/ObjectCreationExpressionSignatureHelpProvider_NormalType.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/ObjectCreationExpressionSignatureHelpProvider_NormalType.cs @@ -51,8 +51,5 @@ private static IList GetNormalTypePreambleParts( } private static IList GetNormalTypePostambleParts() - { - return SpecializedCollections.SingletonList( - Punctuation(SyntaxKind.CloseParenToken)); - } + => [Punctuation(SyntaxKind.CloseParenToken)]; } diff --git a/src/Features/CSharp/Portable/SignatureHelp/PrimaryConstructorBaseTypeSignatureHelpProvider.cs b/src/Features/CSharp/Portable/SignatureHelp/PrimaryConstructorBaseTypeSignatureHelpProvider.cs index 906b176d878c7..d12404451b2f2 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/PrimaryConstructorBaseTypeSignatureHelpProvider.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/PrimaryConstructorBaseTypeSignatureHelpProvider.cs @@ -151,8 +151,6 @@ static IList GetPreambleParts( } static IList GetPostambleParts() - { - return SpecializedCollections.SingletonList(Punctuation(SyntaxKind.CloseParenToken)); - } + => [Punctuation(SyntaxKind.CloseParenToken)]; } } diff --git a/src/Features/CSharp/Portable/Snippets/AbstractCSharpAutoPropertySnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/AbstractCSharpAutoPropertySnippetProvider.cs index bcc6c780bf9e7..e43a703347ccc 100644 --- a/src/Features/CSharp/Portable/Snippets/AbstractCSharpAutoPropertySnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/AbstractCSharpAutoPropertySnippetProvider.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -13,7 +12,6 @@ using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Snippets; @@ -22,7 +20,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; -internal abstract class AbstractCSharpAutoPropertySnippetProvider : AbstractPropertySnippetProvider +using static CSharpSyntaxTokens; + +internal abstract class AbstractCSharpAutoPropertySnippetProvider : AbstractPropertySnippetProvider { protected virtual AccessorDeclarationSyntax? GenerateGetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator) => (AccessorDeclarationSyntax)generator.GetAccessorDeclaration(); @@ -36,7 +36,7 @@ protected override bool IsValidSnippetLocation(in SnippetContext context, Cancel SyntaxKindSet.AllMemberModifiers, SyntaxKindSet.ClassInterfaceStructRecordTypeDeclarations, canBePartial: true, cancellationToken); } - protected override async Task GenerateSnippetSyntaxAsync(Document document, int position, CancellationToken cancellationToken) + protected override async Task GenerateSnippetSyntaxAsync(Document document, int position, CancellationToken cancellationToken) { var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); @@ -55,7 +55,7 @@ protected override async Task GenerateSnippetSyntaxAsync(Document do // If there are no preceding accessibility modifiers create default `public` one if (!syntaxContext.PrecedingModifiers.Any(SyntaxFacts.IsAccessibilityModifier)) { - modifiers = SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + modifiers = SyntaxTokenList.Create(PublicKeyword); } return SyntaxFactory.PropertyDeclaration( @@ -67,25 +67,22 @@ protected override async Task GenerateSnippetSyntaxAsync(Document do accessorList: SyntaxFactory.AccessorList([.. accessors.Where(a => a is not null)!])); } - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) - { - var propertyDeclaration = (PropertyDeclarationSyntax)caretTarget; - return propertyDeclaration.AccessorList!.CloseBraceToken.Span.End; - } + protected override int GetTargetCaretPosition(PropertyDeclarationSyntax propertyDeclaration, SourceText sourceText) + => propertyDeclaration.AccessorList!.CloseBraceToken.Span.End; - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected override ImmutableArray GetPlaceHolderLocationsList(PropertyDeclarationSyntax propertyDeclaration, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); - var propertyDeclaration = (PropertyDeclarationSyntax)node; var identifier = propertyDeclaration.Identifier; var type = propertyDeclaration.Type; - arrayBuilder.Add(new SnippetPlaceholder(type.ToString(), type.SpanStart)); - arrayBuilder.Add(new SnippetPlaceholder(identifier.ValueText, identifier.SpanStart)); - return arrayBuilder.ToImmutableArray(); + return + [ + new SnippetPlaceholder(type.ToString(), type.SpanStart), + new SnippetPlaceholder(identifier.ValueText, identifier.SpanStart), + ]; } - protected override SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, Func isCorrectContainer) + protected override PropertyDeclarationSyntax? FindAddedSnippetSyntaxNode(SyntaxNode root, int position) { var node = root.FindNode(TextSpan.FromBounds(position, position)); return node.GetAncestorOrThis(); diff --git a/src/Features/CSharp/Portable/Snippets/AbstractCSharpForLoopSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/AbstractCSharpForLoopSnippetProvider.cs index 333159bd02d46..ac339ddcd4b0f 100644 --- a/src/Features/CSharp/Portable/Snippets/AbstractCSharpForLoopSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/AbstractCSharpForLoopSnippetProvider.cs @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; using static SyntaxFactory; -internal abstract class AbstractCSharpForLoopSnippetProvider : AbstractForLoopSnippetProvider +internal abstract class AbstractCSharpForLoopSnippetProvider : AbstractForLoopSnippetProvider { private static readonly string[] s_iteratorBaseNames = ["i", "j", "k"]; @@ -38,7 +38,7 @@ internal abstract class AbstractCSharpForLoopSnippetProvider : AbstractForLoopSn protected abstract void AddSpecificPlaceholders(MultiDictionary placeholderBuilder, ExpressionSyntax initializer, ExpressionSyntax rightOfCondition); - protected override SyntaxNode GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo) + protected override ForStatementSyntax GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo) { var semanticModel = syntaxContext.SemanticModel; var compilation = semanticModel.Compilation; @@ -81,13 +81,15 @@ protected override SyntaxNode GenerateStatement(SyntaxGenerator generator, Synta } } - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected override ImmutableArray GetPlaceHolderLocationsList(ForStatementSyntax forStatement, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var result); var placeholderBuilder = new MultiDictionary(); - GetPartsOfForStatement(node, out var declaration, out var condition, out var incrementor, out var _); + var declaration = forStatement.Declaration; + var condition = forStatement.Condition; + var incrementor = forStatement.Incrementors.Single(); - var variableDeclarator = ((VariableDeclarationSyntax)declaration!).Variables.Single(); + var variableDeclarator = declaration!.Variables.Single(); var declaratorIdentifier = variableDeclarator.Identifier; placeholderBuilder.Add(declaratorIdentifier.ValueText, declaratorIdentifier.SpanStart); @@ -106,25 +108,16 @@ protected override ImmutableArray GetPlaceHolderLocationsLis return result.ToImmutableAndClear(); } - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) - => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( - caretTarget, + protected override int GetTargetCaretPosition(ForStatementSyntax forStatement, SourceText sourceText) + => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( + forStatement, static s => (BlockSyntax)s.Statement, sourceText); - protected override Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) - => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( + protected override Task AddIndentationToDocumentAsync(Document document, ForStatementSyntax forStatement, CancellationToken cancellationToken) + => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( document, - FindSnippetAnnotation, + forStatement, static s => (BlockSyntax)s.Statement, cancellationToken); - - private static void GetPartsOfForStatement(SyntaxNode node, out SyntaxNode? declaration, out SyntaxNode? condition, out SyntaxNode? incrementor, out SyntaxNode? statement) - { - var forStatement = (ForStatementSyntax)node; - declaration = forStatement.Declaration; - condition = forStatement.Condition; - incrementor = forStatement.Incrementors.Single(); - statement = forStatement.Statement; - } } diff --git a/src/Features/CSharp/Portable/Snippets/AbstractCSharpMainMethodSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/AbstractCSharpMainMethodSnippetProvider.cs index 2a8cecf5d794a..aa84a9a5061c8 100644 --- a/src/Features/CSharp/Portable/Snippets/AbstractCSharpMainMethodSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/AbstractCSharpMainMethodSnippetProvider.cs @@ -4,6 +4,7 @@ using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Snippets; @@ -11,7 +12,8 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; -internal abstract class AbstractCSharpMainMethodSnippetProvider : AbstractMainMethodSnippetProvider +internal abstract class AbstractCSharpMainMethodSnippetProvider + : AbstractMainMethodSnippetProvider { protected override bool IsValidSnippetLocation(in SnippetContext context, CancellationToken cancellationToken) { diff --git a/src/Features/CSharp/Portable/Snippets/AbstractCSharpTypeSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/AbstractCSharpTypeSnippetProvider.cs index 3f07f8c9a589f..97a89bb6be0e1 100644 --- a/src/Features/CSharp/Portable/Snippets/AbstractCSharpTypeSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/AbstractCSharpTypeSnippetProvider.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.Linq; using System.Threading; @@ -24,7 +23,8 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; -internal abstract class AbstractCSharpTypeSnippetProvider : AbstractTypeSnippetProvider +internal abstract class AbstractCSharpTypeSnippetProvider : AbstractTypeSnippetProvider + where TTypeDeclarationSyntax : BaseTypeDeclarationSyntax { protected abstract ISet ValidModifiers { get; } @@ -78,42 +78,34 @@ protected override bool IsValidSnippetLocation(in SnippetContext context, Cancel return new TextChange(TextSpan.FromBounds(targetPosition, targetPosition), SyntaxFacts.GetText(SyntaxKind.PublicKeyword) + " "); } - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) + protected override int GetTargetCaretPosition(TTypeDeclarationSyntax typeDeclaration, SourceText sourceText) { - var typeDeclaration = (BaseTypeDeclarationSyntax)caretTarget; var triviaSpan = typeDeclaration.CloseBraceToken.LeadingTrivia.Span; var line = sourceText.Lines.GetLineFromPosition(triviaSpan.Start); // Getting the location at the end of the line before the newline. return line.Span.End; } - protected override SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, Func isCorrectContainer) + protected override TTypeDeclarationSyntax? FindAddedSnippetSyntaxNode(SyntaxNode root, int position) { var node = root.FindNode(TextSpan.FromBounds(position, position)); - return node.GetAncestorOrThis(); + return node.GetAncestorOrThis(); } - protected override async Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) + protected override async Task AddIndentationToDocumentAsync(Document document, TTypeDeclarationSyntax typeDeclaration, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var snippet = root.GetAnnotatedNodes(FindSnippetAnnotation).FirstOrDefault(); - - if (snippet is not BaseTypeDeclarationSyntax originalTypeDeclaration) - return document; var syntaxFormattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions: null, cancellationToken).ConfigureAwait(false); - var indentationString = CSharpSnippetHelpers.GetBlockLikeIndentationString(document, originalTypeDeclaration.OpenBraceToken.SpanStart, syntaxFormattingOptions, cancellationToken); + var indentationString = CSharpSnippetHelpers.GetBlockLikeIndentationString(document, typeDeclaration.OpenBraceToken.SpanStart, syntaxFormattingOptions, cancellationToken); - var newTypeDeclaration = originalTypeDeclaration.WithCloseBraceToken( - originalTypeDeclaration.CloseBraceToken.WithPrependedLeadingTrivia(SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, indentationString))); + var newTypeDeclaration = typeDeclaration.WithCloseBraceToken( + typeDeclaration.CloseBraceToken.WithPrependedLeadingTrivia(SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, indentationString))); - var newRoot = root.ReplaceNode(originalTypeDeclaration, newTypeDeclaration.WithAdditionalAnnotations(CursorAnnotation, FindSnippetAnnotation)); + var newRoot = root.ReplaceNode(typeDeclaration, newTypeDeclaration.WithAdditionalAnnotations(FindSnippetAnnotation)); return document.WithSyntaxRoot(newRoot); } - protected override void GetTypeDeclarationIdentifier(SyntaxNode node, out SyntaxToken identifier) - { - var typeDeclaration = (BaseTypeDeclarationSyntax)node; - identifier = typeDeclaration.Identifier; - } + protected sealed override SyntaxToken GetTypeDeclarationIdentifier(TTypeDeclarationSyntax baseTypeDeclaration) + => baseTypeDeclaration.Identifier; } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpClassSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpClassSnippetProvider.cs index 60f1e7e449909..b7c75eab179d7 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpClassSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpClassSnippetProvider.cs @@ -7,9 +7,9 @@ using System.Composition; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Snippets; @@ -18,7 +18,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpClassSnippetProvider : AbstractCSharpTypeSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpClassSnippetProvider() : AbstractCSharpTypeSnippetProvider { private static readonly ISet s_validModifiers = new HashSet(SyntaxFacts.EqualityComparer) { @@ -34,29 +36,18 @@ internal sealed class CSharpClassSnippetProvider : AbstractCSharpTypeSnippetProv SyntaxKind.FileKeyword, }; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpClassSnippetProvider() - { - } - - public override string Identifier => "class"; + public override string Identifier => CSharpSnippetIdentifiers.Class; public override string Description => FeaturesResources.class_; protected override ISet ValidModifiers => s_validModifiers; - protected override async Task GenerateTypeDeclarationAsync(Document document, int position, CancellationToken cancellationToken) + protected override async Task GenerateTypeDeclarationAsync(Document document, int position, CancellationToken cancellationToken) { var generator = SyntaxGenerator.GetGenerator(document); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var name = NameGenerator.GenerateUniqueName("MyClass", name => semanticModel.LookupSymbols(position, name: name).IsEmpty); - return generator.ClassDeclaration(name); - } - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) - { - return syntaxFacts.IsClassDeclaration; + return (ClassDeclarationSyntax)generator.ClassDeclaration(name); } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs index 42fe433a5b59e..fbbf714157efa 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs @@ -4,6 +4,7 @@ using System; using System.Composition; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Snippets; using Microsoft.CodeAnalysis.Snippets.SnippetProviders; @@ -13,6 +14,17 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class CSharpConsoleSnippetProvider() : AbstractConsoleSnippetProvider +internal sealed class CSharpConsoleSnippetProvider() : AbstractConsoleSnippetProvider< + ExpressionStatementSyntax, + ExpressionSyntax, + ArgumentListSyntax> { + protected override ExpressionSyntax GetExpression(ExpressionStatementSyntax expressionStatement) + => expressionStatement.Expression; + + protected override ArgumentListSyntax GetArgumentList(ExpressionSyntax expression) + => ((InvocationExpressionSyntax)expression).ArgumentList; + + protected override SyntaxToken GetOpenParenToken(ArgumentListSyntax argumentList) + => argumentList.OpenParenToken; } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpConstructorSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpConstructorSnippetProvider.cs index 986e051f00d80..0d056f1e8964d 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpConstructorSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpConstructorSnippetProvider.cs @@ -24,7 +24,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpConstructorSnippetProvider : AbstractConstructorSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpConstructorSnippetProvider() : AbstractConstructorSnippetProvider { private static readonly ISet s_validModifiers = new HashSet(SyntaxFacts.EqualityComparer) { @@ -35,12 +37,6 @@ internal sealed class CSharpConstructorSnippetProvider : AbstractConstructorSnip SyntaxKind.StaticKeyword, }; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpConstructorSnippetProvider() - { - } - protected override bool IsValidSnippetLocation(in SnippetContext context, CancellationToken cancellationToken) { var syntaxContext = (CSharpSyntaxContext)context.SyntaxContext; @@ -80,20 +76,16 @@ protected override async Task GenerateSnippetTextChangeAsync(Documen return new TextChange(TextSpan.FromBounds(position, position), constructorDeclaration.NormalizeWhitespace().ToFullString()); } - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) - { - return CSharpSnippetHelpers.GetTargetCaretPositionInBlock( - caretTarget, + protected override int GetTargetCaretPosition(ConstructorDeclarationSyntax constructorDeclaration, SourceText sourceText) + => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( + constructorDeclaration, static d => d.Body!, sourceText); - } - protected override Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) - { - return CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( + protected override Task AddIndentationToDocumentAsync(Document document, ConstructorDeclarationSyntax constructorDeclaration, CancellationToken cancellationToken) + => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( document, - FindSnippetAnnotation, + constructorDeclaration, static d => d.Body!, cancellationToken); - } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpDoWhileLoopStatementProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpDoWhileLoopStatementProvider.cs new file mode 100644 index 0000000000000..5826e10746ef8 --- /dev/null +++ b/src/Features/CSharp/Portable/Snippets/CSharpDoWhileLoopStatementProvider.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.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.CSharp.Snippets; + +[ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpDoWhileLoopStatementProvider() + : AbstractConditionalBlockSnippetProvider +{ + public override string Identifier => CSharpSnippetIdentifiers.Do; + + public override string Description => CSharpFeaturesResources.do_while_loop; + + protected override DoStatementSyntax GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo) + { + return SyntaxFactory.DoStatement( + SyntaxFactory.Block(), + (ExpressionSyntax)(inlineExpressionInfo?.Node.WithoutLeadingTrivia() ?? generator.TrueLiteralExpression())); + } + + protected override ExpressionSyntax GetCondition(DoStatementSyntax node) + => node.Condition; + + protected override int GetTargetCaretPosition(DoStatementSyntax doStatement, SourceText sourceText) + => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( + doStatement, + static s => (BlockSyntax)s.Statement, + sourceText); + + protected override Task AddIndentationToDocumentAsync(Document document, DoStatementSyntax doStatement, CancellationToken cancellationToken) + => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( + document, + doStatement, + static s => (BlockSyntax)s.Statement, + cancellationToken); +} diff --git a/src/Features/CSharp/Portable/Snippets/CSharpElseSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpElseSnippetProvider.cs index f7edde19d6865..eb3270d9d8052 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpElseSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpElseSnippetProvider.cs @@ -17,13 +17,13 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal class CSharpElseSnippetProvider : AbstractElseSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpElseSnippetProvider() : AbstractElseSnippetProvider { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpElseSnippetProvider() - { - } + public override string Identifier => CSharpSnippetIdentifiers.Else; + + public override string Description => FeaturesResources.else_statement; protected override bool IsValidSnippetLocation(in SnippetContext context, CancellationToken cancellationToken) { @@ -61,20 +61,16 @@ protected override Task GenerateSnippetTextChangeAsync(Document docu return Task.FromResult(new TextChange(TextSpan.FromBounds(position, position), elseClause.ToFullString())); } - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) - { - return CSharpSnippetHelpers.GetTargetCaretPositionInBlock( - caretTarget, + protected override int GetTargetCaretPosition(ElseClauseSyntax elseClause, SourceText sourceText) + => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( + elseClause, static c => (BlockSyntax)c.Statement, sourceText); - } - protected override Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) - { - return CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( + protected override Task AddIndentationToDocumentAsync(Document document, ElseClauseSyntax elseClause, CancellationToken cancellationToken) + => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( document, - FindSnippetAnnotation, + elseClause, static c => (BlockSyntax)c.Statement, cancellationToken); - } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpEnumSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpEnumSnippetProvider.cs index 31509a7bf38db..1ffa575749b5a 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpEnumSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpEnumSnippetProvider.cs @@ -7,9 +7,9 @@ using System.Composition; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Snippets; @@ -18,7 +18,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpEnumSnippetProvider : AbstractCSharpTypeSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpEnumSnippetProvider() : AbstractCSharpTypeSnippetProvider { private static readonly ISet s_validModifiers = new HashSet(SyntaxFacts.EqualityComparer) { @@ -29,28 +31,18 @@ internal sealed class CSharpEnumSnippetProvider : AbstractCSharpTypeSnippetProvi SyntaxKind.FileKeyword, }; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpEnumSnippetProvider() - { - } + public override string Identifier => CSharpSnippetIdentifiers.Enum; - public override string Identifier => "enum"; public override string Description => FeaturesResources.enum_; protected override ISet ValidModifiers => s_validModifiers; - protected override async Task GenerateTypeDeclarationAsync(Document document, int position, CancellationToken cancellationToken) + protected override async Task GenerateTypeDeclarationAsync(Document document, int position, CancellationToken cancellationToken) { var generator = SyntaxGenerator.GetGenerator(document); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var name = NameGenerator.GenerateUniqueName("MyEnum", name => semanticModel.LookupSymbols(position, name: name).IsEmpty); - return generator.EnumDeclaration(name); - } - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) - { - return syntaxFacts.IsEnumDeclaration; + return (EnumDeclarationSyntax)generator.EnumDeclaration(name); } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpForEachLoopSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpForEachLoopSnippetProvider.cs index 67eeec1f62388..1ff99c4351f6d 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpForEachLoopSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpForEachLoopSnippetProvider.cs @@ -22,14 +22,17 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpForEachLoopSnippetProvider : AbstractForEachLoopSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpForEachLoopSnippetProvider() : AbstractForEachLoopSnippetProvider { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpForEachLoopSnippetProvider() - { - } + public override string Identifier => CSharpSnippetIdentifiers.ForEach; + + public override string Description => FeaturesResources.foreach_loop; protected override bool IsValidSnippetLocation(in SnippetContext context, CancellationToken cancellationToken) { @@ -48,12 +51,12 @@ protected override bool IsValidSnippetLocation(in SnippetContext context, Cancel return base.IsValidSnippetLocation(in context, cancellationToken); } - protected override SyntaxNode GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo) + protected override ForEachStatementSyntax GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo) { var semanticModel = syntaxContext.SemanticModel; var position = syntaxContext.Position; - var varIdentifier = SyntaxFactory.IdentifierName("var"); + var varIdentifier = IdentifierName("var"); var collectionIdentifier = (ExpressionSyntax?)inlineExpressionInfo?.Node; if (collectionIdentifier is null) @@ -63,8 +66,8 @@ protected override SyntaxNode GenerateStatement(SyntaxGenerator generator, Synta (isAsync ? symbolType.CanBeAsynchronouslyEnumerated(semanticModel.Compilation) : symbolType.CanBeEnumerated()) && symbol.Kind is SymbolKind.Local or SymbolKind.Field or SymbolKind.Parameter or SymbolKind.Property); collectionIdentifier = enumerationSymbol is null - ? SyntaxFactory.IdentifierName("collection") - : SyntaxFactory.IdentifierName(enumerationSymbol.Name); + ? IdentifierName("collection") + : IdentifierName(enumerationSymbol.Name); } var itemString = NameGenerator.GenerateUniqueName( @@ -75,24 +78,24 @@ protected override SyntaxNode GenerateStatement(SyntaxGenerator generator, Synta if (inlineExpressionInfo is { TypeInfo: var typeInfo } && typeInfo.Type!.CanBeAsynchronouslyEnumerated(semanticModel.Compilation)) { - forEachStatement = SyntaxFactory.ForEachStatement( - SyntaxFactory.Token(SyntaxKind.AwaitKeyword), - SyntaxFactory.Token(SyntaxKind.ForEachKeyword), - SyntaxFactory.Token(SyntaxKind.OpenParenToken), + forEachStatement = ForEachStatement( + AwaitKeyword, + ForEachKeyword, + OpenParenToken, varIdentifier, - SyntaxFactory.Identifier(itemString), - SyntaxFactory.Token(SyntaxKind.InKeyword), + Identifier(itemString), + InKeyword, collectionIdentifier.WithoutLeadingTrivia(), - SyntaxFactory.Token(SyntaxKind.CloseParenToken), - SyntaxFactory.Block()); + CloseParenToken, + Block()); } else { - forEachStatement = SyntaxFactory.ForEachStatement( + forEachStatement = ForEachStatement( varIdentifier, itemString, collectionIdentifier.WithoutLeadingTrivia(), - SyntaxFactory.Block()); + Block()); } return forEachStatement.NormalizeWhitespace(); @@ -102,40 +105,27 @@ protected override SyntaxNode GenerateStatement(SyntaxGenerator generator, Synta /// Goes through each piece of the foreach statement and extracts the identifiers /// as well as their locations to create SnippetPlaceholder's of each. /// - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected override ImmutableArray GetPlaceHolderLocationsList(ForEachStatementSyntax node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); - GetPartsOfForEachStatement(node, out var identifier, out var expression, out var _1); - arrayBuilder.Add(new SnippetPlaceholder(identifier.ToString(), identifier.SpanStart)); + arrayBuilder.Add(new SnippetPlaceholder(node.Identifier.ToString(), node.Identifier.SpanStart)); if (!ConstructedFromInlineExpression) - arrayBuilder.Add(new SnippetPlaceholder(expression.ToString(), expression.SpanStart)); + arrayBuilder.Add(new SnippetPlaceholder(node.Expression.ToString(), node.Expression.SpanStart)); - return arrayBuilder.ToImmutableArray(); + return arrayBuilder.ToImmutableAndClear(); } - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) - { - return CSharpSnippetHelpers.GetTargetCaretPositionInBlock( - caretTarget, + protected override int GetTargetCaretPosition(ForEachStatementSyntax forEachStatement, SourceText sourceText) + => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( + forEachStatement, static s => (BlockSyntax)s.Statement, sourceText); - } - protected override Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) - { - return CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( + protected override Task AddIndentationToDocumentAsync(Document document, ForEachStatementSyntax forEachStatement, CancellationToken cancellationToken) + => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( document, - FindSnippetAnnotation, + forEachStatement, static s => (BlockSyntax)s.Statement, cancellationToken); - } - - private static void GetPartsOfForEachStatement(SyntaxNode node, out SyntaxToken identifier, out SyntaxNode expression, out SyntaxNode statement) - { - var forEachStatement = (ForEachStatementSyntax)node; - identifier = forEachStatement.Identifier; - expression = forEachStatement.Expression; - statement = forEachStatement.Statement; - } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpForLoopSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpForLoopSnippetProvider.cs index f29cb1373e30c..7ea6feaf03f60 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpForLoopSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpForLoopSnippetProvider.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class CSharpForLoopSnippetProvider() : AbstractCSharpForLoopSnippetProvider { - public override string Identifier => "for"; + public override string Identifier => CSharpSnippetIdentifiers.For; public override string Description => CSharpFeaturesResources.for_loop; diff --git a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs index d826b1ee686fc..aeab4ab617f8a 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs @@ -16,34 +16,27 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpIfSnippetProvider : AbstractIfSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpIfSnippetProvider() : AbstractIfSnippetProvider { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpIfSnippetProvider() - { - } + public override string Identifier => CSharpSnippetIdentifiers.If; - protected override SyntaxNode GetCondition(SyntaxNode node) - { - var ifStatement = (IfStatementSyntax)node; - return ifStatement.Condition; - } + public override string Description => FeaturesResources.if_statement; - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) - { - return CSharpSnippetHelpers.GetTargetCaretPositionInBlock( - caretTarget, + protected override ExpressionSyntax GetCondition(IfStatementSyntax node) + => node.Condition; + + protected override int GetTargetCaretPosition(IfStatementSyntax ifStatement, SourceText sourceText) + => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( + ifStatement, static s => (BlockSyntax)s.Statement, sourceText); - } - protected override Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) - { - return CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( + protected override Task AddIndentationToDocumentAsync(Document document, IfStatementSyntax ifStatement, CancellationToken cancellationToken) + => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( document, - FindSnippetAnnotation, + ifStatement, static s => (BlockSyntax)s.Statement, cancellationToken); - } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpIntMainSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpIntMainSnippetProvider.cs index 5d69ace161a82..9bf964e8bbe31 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpIntMainSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpIntMainSnippetProvider.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Composition; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -17,35 +16,29 @@ using Microsoft.CodeAnalysis.Snippets; using Microsoft.CodeAnalysis.Snippets.SnippetProviders; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpIntMainSnippetProvider : AbstractCSharpMainMethodSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpIntMainSnippetProvider() : AbstractCSharpMainMethodSnippetProvider { - public override string Identifier => "sim"; + public override string Identifier => CSharpSnippetIdentifiers.StaticIntMain; public override string Description => CSharpFeaturesResources.static_int_Main; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpIntMainSnippetProvider() - { - } + protected override TypeSyntax GenerateReturnType(SyntaxGenerator generator) + => (TypeSyntax)generator.TypeExpression(SpecialType.System_Int32); - protected override SyntaxNode GenerateReturnType(SyntaxGenerator generator) - => generator.TypeExpression(SpecialType.System_Int32); - - protected override IEnumerable GenerateInnerStatements(SyntaxGenerator generator) + protected override IEnumerable GenerateInnerStatements(SyntaxGenerator generator) { - var returnStatement = generator.ReturnStatement(generator.LiteralExpression(0)); - return SpecializedCollections.SingletonEnumerable(returnStatement); + var returnStatement = (StatementSyntax)generator.ReturnStatement(generator.LiteralExpression(0)); + return [returnStatement]; } - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) + protected override int GetTargetCaretPosition(MethodDeclarationSyntax methodDeclaration, SourceText sourceText) { - var methodDeclaration = (MethodDeclarationSyntax)caretTarget; var body = methodDeclaration.Body!; var returnStatement = body.Statements.First(); @@ -55,13 +48,9 @@ protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, S return line.Span.End; } - protected override async Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) + protected override async Task AddIndentationToDocumentAsync(Document document, MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var snippetNode = root.GetAnnotatedNodes(FindSnippetAnnotation).FirstOrDefault(); - - if (snippetNode is not MethodDeclarationSyntax methodDeclaration) - return document; var body = methodDeclaration.Body!; var returnStatement = body.Statements.First(); diff --git a/src/Features/CSharp/Portable/Snippets/CSharpInterfaceSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpInterfaceSnippetProvider.cs index 6774f33d9a7e7..a35d3d72e4a9f 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpInterfaceSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpInterfaceSnippetProvider.cs @@ -7,9 +7,9 @@ using System.Composition; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Snippets; @@ -18,7 +18,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpInterfaceSnippetProvider : AbstractCSharpTypeSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpInterfaceSnippetProvider() : AbstractCSharpTypeSnippetProvider { private static readonly ISet s_validModifiers = new HashSet(SyntaxFacts.EqualityComparer) { @@ -30,29 +32,18 @@ internal sealed class CSharpInterfaceSnippetProvider : AbstractCSharpTypeSnippet SyntaxKind.FileKeyword, }; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpInterfaceSnippetProvider() - { - } - - public override string Identifier => "interface"; + public override string Identifier => CSharpSnippetIdentifiers.Interface; public override string Description => FeaturesResources.interface_; protected override ISet ValidModifiers => s_validModifiers; - protected override async Task GenerateTypeDeclarationAsync(Document document, int position, CancellationToken cancellationToken) + protected override async Task GenerateTypeDeclarationAsync(Document document, int position, CancellationToken cancellationToken) { var generator = SyntaxGenerator.GetGenerator(document); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var name = NameGenerator.GenerateUniqueName("MyInterface", name => semanticModel.LookupSymbols(position, name: name).IsEmpty); - return generator.InterfaceDeclaration(name); - } - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) - { - return syntaxFacts.IsInterfaceDeclaration; + return (InterfaceDeclarationSyntax)generator.InterfaceDeclaration(name); } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpLockSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpLockSnippetProvider.cs index 0ef47e1701195..b589851f93713 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpLockSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpLockSnippetProvider.cs @@ -17,39 +17,30 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpLockSnippetProvider : AbstractLockSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpLockSnippetProvider() : AbstractLockSnippetProvider { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpLockSnippetProvider() - { - } - - public override string Identifier => "lock"; + public override string Identifier => CSharpSnippetIdentifiers.Lock; public override string Description => CSharpFeaturesResources.lock_statement; - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected override ImmutableArray GetPlaceHolderLocationsList(LockStatementSyntax node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - var lockStatement = (LockStatementSyntax)node; - var expression = lockStatement.Expression; + var expression = node.Expression; return [new SnippetPlaceholder(expression.ToString(), expression.SpanStart)]; } - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) - { - return CSharpSnippetHelpers.GetTargetCaretPositionInBlock( - caretTarget, + protected override int GetTargetCaretPosition(LockStatementSyntax lockStatement, SourceText sourceText) + => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( + lockStatement, static s => (BlockSyntax)s.Statement, sourceText); - } - protected override Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) - { - return CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( + protected override Task AddIndentationToDocumentAsync(Document document, LockStatementSyntax lockStatement, CancellationToken cancellationToken) + => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( document, - FindSnippetAnnotation, + lockStatement, static s => (BlockSyntax)s.Statement, cancellationToken); - } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpPropSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpPropSnippetProvider.cs index 786e6d5be97a5..64d2ab7a1a40e 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpPropSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpPropSnippetProvider.cs @@ -14,15 +14,11 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpPropSnippetProvider : AbstractCSharpAutoPropertySnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpPropSnippetProvider() : AbstractCSharpAutoPropertySnippetProvider { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpPropSnippetProvider() - { - } - - public override string Identifier => "prop"; + public override string Identifier => CommonSnippetIdentifiers.Property; public override string Description => FeaturesResources.property_; diff --git a/src/Features/CSharp/Portable/Snippets/CSharpPropgSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpPropgSnippetProvider.cs index 8a96f8423b0ad..5bc0832910a7b 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpPropgSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpPropgSnippetProvider.cs @@ -14,15 +14,11 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal class CSharpPropgSnippetProvider : AbstractCSharpAutoPropertySnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpPropgSnippetProvider() : AbstractCSharpAutoPropertySnippetProvider { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpPropgSnippetProvider() - { - } - - public override string Identifier => "propg"; + public override string Identifier => CommonSnippetIdentifiers.GetOnlyProperty; public override string Description => FeaturesResources.get_only_property; diff --git a/src/Features/CSharp/Portable/Snippets/CSharpPropiSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpPropiSnippetProvider.cs index 7e2f6a5523769..92d406ba54fdb 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpPropiSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpPropiSnippetProvider.cs @@ -13,19 +13,17 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; +using static CSharpSyntaxTokens; + [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal class CSharpPropiSnippetProvider : AbstractCSharpAutoPropertySnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpPropiSnippetProvider() : AbstractCSharpAutoPropertySnippetProvider { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpPropiSnippetProvider() - { - } - - public override string Identifier => "propi"; + public override string Identifier => CSharpSnippetIdentifiers.InitOnlyProperty; public override string Description => CSharpFeaturesResources.init_only_property; protected override AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator) - => SyntaxFactory.AccessorDeclaration(SyntaxKind.InitAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + => SyntaxFactory.AccessorDeclaration(SyntaxKind.InitAccessorDeclaration).WithSemicolonToken(SemicolonToken); } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpReversedForLoopSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpReversedForLoopSnippetProvider.cs index 0eb42d90c84f0..1c05b3df2ac6b 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpReversedForLoopSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpReversedForLoopSnippetProvider.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class CSharpReversedForLoopSnippetProvider() : AbstractCSharpForLoopSnippetProvider { - public override string Identifier => "forr"; + public override string Identifier => CSharpSnippetIdentifiers.ReversedFor; public override string Description => CSharpFeaturesResources.reversed_for_loop; diff --git a/src/Features/CSharp/Portable/Snippets/CSharpSnippetFunctionService.cs b/src/Features/CSharp/Portable/Snippets/CSharpSnippetFunctionService.cs index 2e152fd85737a..b29c82ef669b7 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpSnippetFunctionService.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpSnippetFunctionService.cs @@ -14,14 +14,10 @@ namespace Microsoft.CodeAnalysis.CSharp; [ExportLanguageService(typeof(SnippetFunctionService), LanguageNames.CSharp), Shared] -internal class CSharpSnippetFunctionService : SnippetFunctionService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpSnippetFunctionService() : SnippetFunctionService { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpSnippetFunctionService() - { - } - public override async Task GetContainingClassNameAsync(Document document, int position, CancellationToken cancellationToken) { // Find the nearest enclosing type declaration and use its name diff --git a/src/Features/CSharp/Portable/Snippets/CSharpSnippetHelpers.cs b/src/Features/CSharp/Portable/Snippets/CSharpSnippetHelpers.cs index a4865ed01a04e..76a64bf2a6c14 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpSnippetHelpers.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpSnippetHelpers.cs @@ -16,11 +16,10 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; internal static class CSharpSnippetHelpers { - public static int GetTargetCaretPositionInBlock(SyntaxNode caretTarget, Func getBlock, SourceText sourceText) + public static int GetTargetCaretPositionInBlock(TTargetNode caretTarget, Func getBlock, SourceText sourceText) where TTargetNode : SyntaxNode { - var targetNode = (TTargetNode)caretTarget; - var block = getBlock(targetNode); + var block = getBlock(caretTarget); var triviaSpan = block.CloseBraceToken.LeadingTrivia.Span; var line = sourceText.Lines.GetLineFromPosition(triviaSpan.Start); @@ -44,24 +43,20 @@ public static string GetBlockLikeIndentationString(Document document, int startP return newIndentation.GetIndentationString(parsedDocument.Text, syntaxFormattingOptions.UseTabs, syntaxFormattingOptions.TabSize) + newLine; } - public static async Task AddBlockIndentationToDocumentAsync(Document document, SyntaxAnnotation findSnippetAnnotation, Func getBlock, CancellationToken cancellationToken) + public static async Task AddBlockIndentationToDocumentAsync( + Document document, TTargetNode targetNode, Func getBlock, CancellationToken cancellationToken) where TTargetNode : SyntaxNode { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var snippetNode = root.GetAnnotatedNodes(findSnippetAnnotation).FirstOrDefault(); - - if (snippetNode is not TTargetNode targetStatement) - return document; - - var block = getBlock(targetStatement); + var block = getBlock(targetNode); var syntaxFormattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions: null, cancellationToken).ConfigureAwait(false); var indentationString = GetBlockLikeIndentationString(document, block.SpanStart, syntaxFormattingOptions, cancellationToken); var updatedBlock = block.WithCloseBraceToken(block.CloseBraceToken.WithPrependedLeadingTrivia(SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, indentationString))); - var updatedTargetStatement = targetStatement.ReplaceNode(block, updatedBlock); + var updatedTargetStatement = targetNode.ReplaceNode(block, updatedBlock); - var newRoot = root.ReplaceNode(targetStatement, updatedTargetStatement); + var newRoot = root.ReplaceNode(targetNode, updatedTargetStatement); return document.WithSyntaxRoot(newRoot); } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpSnippetIdentifiers.cs b/src/Features/CSharp/Portable/Snippets/CSharpSnippetIdentifiers.cs new file mode 100644 index 0000000000000..f2c39f292e8d3 --- /dev/null +++ b/src/Features/CSharp/Portable/Snippets/CSharpSnippetIdentifiers.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. + +namespace Microsoft.CodeAnalysis.CSharp.Snippets; + +/// +/// Contains C#-specific snippet identifiers. +/// These either are C# keywords, contain C# keywords or represent C#-only language constructs +/// +internal static class CSharpSnippetIdentifiers +{ + public const string Class = "class"; + public const string Do = "do"; + public const string Else = "else"; + public const string Enum = "enum"; + public const string For = "for"; + public const string ReversedFor = "forr"; + public const string ForEach = "foreach"; + public const string InitOnlyProperty = "propi"; + public const string If = "if"; + public const string Interface = "interface"; + public const string Lock = "lock"; + public const string StaticIntMain = "sim"; + public const string Struct = "struct"; + public const string StaticVoidMain = "svm"; + public const string While = "while"; +} diff --git a/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs b/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs index 16590ff577a6c..462f2a8e921d9 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs @@ -14,6 +14,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportLanguageService(typeof(ISnippetService), LanguageNames.CSharp), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class CSharpSnippetService([ImportMany] IEnumerable> snippetProviders) : AbstractSnippetService(snippetProviders) +internal sealed class CSharpSnippetService([ImportMany] IEnumerable> snippetProviders) : AbstractSnippetService(snippetProviders) { } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpStructSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpStructSnippetProvider.cs index 6a208cd578d57..d7661e4f466ed 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpStructSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpStructSnippetProvider.cs @@ -7,9 +7,9 @@ using System.Composition; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Snippets; @@ -18,7 +18,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpStructSnippetProvider : AbstractCSharpTypeSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpStructSnippetProvider() : AbstractCSharpTypeSnippetProvider { private static readonly ISet s_validModifiers = new HashSet(SyntaxFacts.EqualityComparer) { @@ -32,29 +34,18 @@ internal sealed class CSharpStructSnippetProvider : AbstractCSharpTypeSnippetPro SyntaxKind.FileKeyword, }; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpStructSnippetProvider() - { - } - - public override string Identifier => "struct"; + public override string Identifier => CSharpSnippetIdentifiers.Struct; public override string Description => FeaturesResources.struct_; protected override ISet ValidModifiers => s_validModifiers; - protected override async Task GenerateTypeDeclarationAsync(Document document, int position, CancellationToken cancellationToken) + protected override async Task GenerateTypeDeclarationAsync(Document document, int position, CancellationToken cancellationToken) { var generator = SyntaxGenerator.GetGenerator(document); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var name = NameGenerator.GenerateUniqueName("MyStruct", name => semanticModel.LookupSymbols(position, name: name).IsEmpty); - return generator.StructDeclaration(name); - } - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) - { - return syntaxFacts.IsStructDeclaration; + return (StructDeclarationSyntax)generator.StructDeclaration(name); } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpVoidMainSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpVoidMainSnippetProvider.cs index 036c046cd3230..41e853577cc17 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpVoidMainSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpVoidMainSnippetProvider.cs @@ -10,47 +10,39 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Snippets; using Microsoft.CodeAnalysis.Snippets.SnippetProviders; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Snippets; +using static CSharpSyntaxTokens; + [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpVoidMainSnippetProvider : AbstractCSharpMainMethodSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpVoidMainSnippetProvider() : AbstractCSharpMainMethodSnippetProvider { - public override string Identifier => "svm"; + public override string Identifier => CSharpSnippetIdentifiers.StaticVoidMain; public override string Description => CSharpFeaturesResources.static_void_Main; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpVoidMainSnippetProvider() - { - } - - protected override SyntaxNode GenerateReturnType(SyntaxGenerator generator) - => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)); + protected override TypeSyntax GenerateReturnType(SyntaxGenerator generator) + => SyntaxFactory.PredefinedType(VoidKeyword); - protected override IEnumerable GenerateInnerStatements(SyntaxGenerator generator) - => SpecializedCollections.EmptyEnumerable(); + protected override IEnumerable GenerateInnerStatements(SyntaxGenerator generator) + => []; - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) - { - return CSharpSnippetHelpers.GetTargetCaretPositionInBlock( - caretTarget, + protected override int GetTargetCaretPosition(MethodDeclarationSyntax methodDeclaration, SourceText sourceText) + => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( + methodDeclaration, static d => d.Body!, sourceText); - } - protected override Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) - { - return CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( + protected override Task AddIndentationToDocumentAsync(Document document, MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken) + => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( document, - FindSnippetAnnotation, + methodDeclaration, static m => m.Body!, cancellationToken); - } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpWhileLoopSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpWhileLoopSnippetProvider.cs index 6f5928a0d38b8..82b360515c93b 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpWhileLoopSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpWhileLoopSnippetProvider.cs @@ -16,34 +16,27 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] -internal sealed class CSharpWhileLoopSnippetProvider : AbstractWhileLoopSnippetProvider +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpWhileLoopSnippetProvider() : AbstractWhileLoopSnippetProvider { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpWhileLoopSnippetProvider() - { - } + public override string Identifier => CSharpSnippetIdentifiers.While; - protected override SyntaxNode GetCondition(SyntaxNode node) - { - var whileStatement = (WhileStatementSyntax)node; - return whileStatement.Condition; - } + public override string Description => FeaturesResources.while_loop; - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) - { - return CSharpSnippetHelpers.GetTargetCaretPositionInBlock( - caretTarget, + protected override ExpressionSyntax GetCondition(WhileStatementSyntax node) + => node.Condition; + + protected override int GetTargetCaretPosition(WhileStatementSyntax whileStatement, SourceText sourceText) + => CSharpSnippetHelpers.GetTargetCaretPositionInBlock( + whileStatement, static s => (BlockSyntax)s.Statement, sourceText); - } - protected override Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) - { - return CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( + protected override Task AddIndentationToDocumentAsync(Document document, WhileStatementSyntax whileStatement, CancellationToken cancellationToken) + => CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync( document, - FindSnippetAnnotation, + whileStatement, static s => (BlockSyntax)s.Statement, cancellationToken); - } } diff --git a/src/Features/CSharp/Portable/SplitOrMergeIfStatements/CSharpIfLikeStatementGenerator.cs b/src/Features/CSharp/Portable/SplitOrMergeIfStatements/CSharpIfLikeStatementGenerator.cs index b9c07e20eea64..5963590fad5e3 100644 --- a/src/Features/CSharp/Portable/SplitOrMergeIfStatements/CSharpIfLikeStatementGenerator.cs +++ b/src/Features/CSharp/Portable/SplitOrMergeIfStatements/CSharpIfLikeStatementGenerator.cs @@ -15,6 +15,8 @@ namespace Microsoft.CodeAnalysis.CSharp.SplitOrMergeIfStatements; +using static SyntaxFactory; + [ExportLanguageService(typeof(IIfLikeStatementGenerator), LanguageNames.CSharp), Shared] internal sealed class CSharpIfLikeStatementGenerator : IIfLikeStatementGenerator { @@ -110,7 +112,7 @@ public SyntaxNode WithCondition(SyntaxNode ifOrElseIf, SyntaxNode condition) public SyntaxNode WithStatementInBlock(SyntaxNode ifOrElseIf, SyntaxNode statement) { var ifStatement = (IfStatementSyntax)ifOrElseIf; - return ifStatement.WithStatement(SyntaxFactory.Block((StatementSyntax)statement)); + return ifStatement.WithStatement(Block((StatementSyntax)statement)); } public SyntaxNode WithStatementsOf(SyntaxNode ifOrElseIf, SyntaxNode otherIfOrElseIf) @@ -137,14 +139,14 @@ public void InsertElseIfClause(SyntaxEditor editor, SyntaxNode afterIfOrElseIf, var elseIfStatement = (IfStatementSyntax)elseIfClause; var newElseIfStatement = elseIfStatement.WithElse(ifStatement.Else); - var newIfStatement = ifStatement.WithElse(SyntaxFactory.ElseClause(newElseIfStatement)); + var newIfStatement = ifStatement.WithElse(ElseClause(newElseIfStatement)); if (ifStatement.Else == null && ContainsEmbeddedIfStatement(ifStatement)) { // If the if statement contains an embedded if statement (not wrapped inside a block), adding an else // clause might introduce a dangling else problem (the 'else' would bind to the inner if statement), // so if there used to be no else clause, we'll insert a new block to prevent that. - newIfStatement = newIfStatement.WithStatement(SyntaxFactory.Block(newIfStatement.Statement)); + newIfStatement = newIfStatement.WithStatement(Block(newIfStatement.Statement)); } return newIfStatement; diff --git a/src/Features/CSharp/Portable/SplitStringLiteral/InterpolatedStringSplitter.cs b/src/Features/CSharp/Portable/SplitStringLiteral/InterpolatedStringSplitter.cs index 6fa367b2bc8a8..da9f9c64ebe72 100644 --- a/src/Features/CSharp/Portable/SplitStringLiteral/InterpolatedStringSplitter.cs +++ b/src/Features/CSharp/Portable/SplitStringLiteral/InterpolatedStringSplitter.cs @@ -8,12 +8,14 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.SplitStringLiteral; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal abstract partial class StringSplitter { private sealed class InterpolatedStringSplitter( @@ -58,18 +60,18 @@ protected override BinaryExpressionSyntax CreateSplitString() } } - var leftExpression = SyntaxFactory.InterpolatedStringExpression( + var leftExpression = InterpolatedStringExpression( _interpolatedStringExpression.StringStartToken, [.. beforeSplitContents], - SyntaxFactory.Token(SyntaxKind.InterpolatedStringEndToken) - .WithTrailingTrivia(SyntaxFactory.ElasticSpace)); + InterpolatedStringEndToken + .WithTrailingTrivia(ElasticSpace)); - var rightExpression = SyntaxFactory.InterpolatedStringExpression( - SyntaxFactory.Token(SyntaxKind.InterpolatedStringStartToken), + var rightExpression = InterpolatedStringExpression( + InterpolatedStringStartToken, [.. afterSplitContents], _interpolatedStringExpression.StringEndToken); - return SyntaxFactory.BinaryExpression( + return BinaryExpression( SyntaxKind.AddExpression, leftExpression, PlusNewLineToken, @@ -79,8 +81,8 @@ protected override BinaryExpressionSyntax CreateSplitString() private InterpolatedStringTextSyntax CreateInterpolatedStringText(int start, int end) { var content = Document.Text.ToString(TextSpan.FromBounds(start, end)); - return SyntaxFactory.InterpolatedStringText( - SyntaxFactory.Token( + return InterpolatedStringText( + Token( leading: default, kind: SyntaxKind.InterpolatedStringTextToken, text: content, diff --git a/src/Features/CSharp/Portable/StringIndentation/CSharpStringIndentationService.cs b/src/Features/CSharp/Portable/StringIndentation/CSharpStringIndentationService.cs index 61b5753854a91..fd5d41cbb8f57 100644 --- a/src/Features/CSharp/Portable/StringIndentation/CSharpStringIndentationService.cs +++ b/src/Features/CSharp/Portable/StringIndentation/CSharpStringIndentationService.cs @@ -37,7 +37,7 @@ public async Task> GetStringIndentationR Recurse(text, root, textSpan, result, cancellationToken); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static void Recurse( diff --git a/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs b/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs index 6a12f660c1851..c96dbb0c61400 100644 --- a/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs @@ -20,6 +20,9 @@ namespace Microsoft.CodeAnalysis.CSharp.UseAutoProperty; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseAutoProperty), Shared] internal class CSharpUseAutoPropertyCodeFixProvider : AbstractUseAutoPropertyCodeFixProvider @@ -49,14 +52,14 @@ protected override async Task UpdatePropertyAsync( var updatedProperty = propertyDeclaration.WithAccessorList(UpdateAccessorList(propertyDeclaration.AccessorList)) .WithExpressionBody(null) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)); + .WithSemicolonToken(Token(SyntaxKind.None)); // We may need to add a setter if the field is written to outside of the constructor // of it's class. if (NeedsSetter(compilation, propertyDeclaration, isWrittenOutsideOfConstructor)) { - var accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + var accessor = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + .WithSemicolonToken(SemicolonToken); var generator = SyntaxGenerator.GetGenerator(project); if (fieldSymbol.DeclaredAccessibility != propertySymbol.DeclaredAccessibility) @@ -64,7 +67,7 @@ protected override async Task UpdatePropertyAsync( accessor = (AccessorDeclarationSyntax)generator.WithAccessibility(accessor, fieldSymbol.DeclaredAccessibility); } - var modifiers = SyntaxFactory.TokenList( + var modifiers = TokenList( updatedProperty.Modifiers.Where(token => !token.IsKind(SyntaxKind.ReadOnlyKeyword))); updatedProperty = updatedProperty.WithModifiers(modifiers) @@ -74,8 +77,8 @@ protected override async Task UpdatePropertyAsync( var fieldInitializer = await GetFieldInitializerAsync(fieldSymbol, cancellationToken).ConfigureAwait(false); if (fieldInitializer != null) { - updatedProperty = updatedProperty.WithInitializer(SyntaxFactory.EqualsValueClause(fieldInitializer)) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + updatedProperty = updatedProperty.WithInitializer(EqualsValueClause(fieldInitializer)) + .WithSemicolonToken(SemicolonToken); } return updatedProperty.WithTrailingTrivia(trailingTrivia).WithAdditionalAnnotations(SpecializedFormattingAnnotation); @@ -164,9 +167,9 @@ private static AccessorListSyntax UpdateAccessorList(AccessorListSyntax accessor { if (accessorList == null) { - var getter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - return SyntaxFactory.AccessorList([getter]); + var getter = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithSemicolonToken(SemicolonToken); + return AccessorList([getter]); } return accessorList.WithAccessors([.. GetAccessors(accessorList.Accessors)]); @@ -178,7 +181,7 @@ private static IEnumerable GetAccessors(SyntaxList> ComputeRefactoringsAsync( { var lambdaNode = await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); if (lambdaNode == null) - { return []; - } var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - using var resultDisposer = ArrayBuilder.GetInstance(out var result); + using var result = TemporaryArray.Empty; if (UseExpressionBodyForLambdaHelpers.CanOfferUseExpressionBody(option, lambdaNode, root.GetLanguageVersion(), cancellationToken)) { var title = UseExpressionBodyForLambdaHelpers.UseExpressionBodyTitle.ToString(); @@ -194,7 +193,7 @@ private static async Task> ComputeRefactoringsAsync( title)); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static async Task UpdateDocumentAsync( diff --git a/src/Features/CSharp/Portable/UseNamedArguments/CSharpUseNamedArgumentsCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/UseNamedArguments/CSharpUseNamedArgumentsCodeRefactoringProvider.cs index f3225cb874cfc..5a621d8268fef 100644 --- a/src/Features/CSharp/Portable/UseNamedArguments/CSharpUseNamedArgumentsCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/UseNamedArguments/CSharpUseNamedArgumentsCodeRefactoringProvider.cs @@ -14,6 +14,8 @@ namespace Microsoft.CodeAnalysis.CSharp.UseNamedArguments; +using static SyntaxFactory; + [ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.IntroduceVariable)] [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.UseNamedArguments), Shared] internal class CSharpUseNamedArgumentsCodeRefactoringProvider : AbstractUseNamedArgumentsCodeRefactoringProvider @@ -66,10 +68,10 @@ protected override SeparatedSyntaxList GetArguments(BaseArgument protected override BaseArgumentListSyntax WithArguments( BaseArgumentListSyntax argumentList, IEnumerable namedArguments, IEnumerable separators) - => argumentList.WithArguments(SyntaxFactory.SeparatedList(namedArguments, separators)); + => argumentList.WithArguments(SeparatedList(namedArguments, separators)); protected override ArgumentSyntax WithName(ArgumentSyntax argument, string name) - => argument.WithNameColon(SyntaxFactory.NameColon(name.ToIdentifierName())); + => argument.WithNameColon(NameColon(name.ToIdentifierName())); protected override ExpressionSyntax GetArgumentExpression(ArgumentSyntax argumentSyntax) => argumentSyntax.Expression; @@ -86,10 +88,10 @@ protected override SeparatedSyntaxList GetArguments(Att protected override AttributeArgumentListSyntax WithArguments( AttributeArgumentListSyntax argumentList, IEnumerable namedArguments, IEnumerable separators) - => argumentList.WithArguments(SyntaxFactory.SeparatedList(namedArguments, separators)); + => argumentList.WithArguments(SeparatedList(namedArguments, separators)); protected override AttributeArgumentSyntax WithName(AttributeArgumentSyntax argument, string name) - => argument.WithNameColon(SyntaxFactory.NameColon(name.ToIdentifierName())); + => argument.WithNameColon(NameColon(name.ToIdentifierName())); protected override ExpressionSyntax GetArgumentExpression(AttributeArgumentSyntax argumentSyntax) => argumentSyntax.Expression; diff --git a/src/Features/CSharp/Portable/UsePatternMatching/CSharpIsAndCastCheckWithoutNameDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UsePatternMatching/CSharpIsAndCastCheckWithoutNameDiagnosticAnalyzer.cs index 3022d60dab8de..df4f081f7b0a3 100644 --- a/src/Features/CSharp/Portable/UsePatternMatching/CSharpIsAndCastCheckWithoutNameDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/UsePatternMatching/CSharpIsAndCastCheckWithoutNameDiagnosticAnalyzer.cs @@ -102,7 +102,7 @@ private void SyntaxNodeAction( Descriptor, isExpression.GetLocation(), styleOption.Notification, context.Options, - SpecializedCollections.EmptyCollection(), + additionalLocations: [], ImmutableDictionary.Empty)); } diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf index d6a1f3ed5ce50..895bf6cf3c3b2 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + Použít {0} Apply Copilot suggestion - Apply Copilot suggestion + Použít návrh Copilotu @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + Použít opravu z @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + Upozornění: Návrhy umělé inteligence můžou být nepřesné. @@ -267,6 +267,11 @@ asynchronní deklarace using {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias externí alias diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf index 14491398a0afd..0ff37d89a6748 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + Anwenden von "{0}" Apply Copilot suggestion - Apply Copilot suggestion + Copilot-Vorschlag anwenden @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + Korrektur anwenden von @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + Warnung: KI-Vorschläge sind möglicherweise ungenau. @@ -267,6 +267,11 @@ asynchrone using-Deklaration {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias externer Alias diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf index 046e19d1f2372..271ad0c92d304 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + Aplicar "{0}" Apply Copilot suggestion - Apply Copilot suggestion + Aplicar sugerencia de Copilot @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + Aplicar corrección desde @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + Advertencia: es posible que las sugerencias de inteligencia artificial no sean precisas. @@ -267,6 +267,11 @@ declaración using asincrónica {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias alias externo diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf index 03d94573bace4..5357e21703139 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + Appliquer « {0} » Apply Copilot suggestion - Apply Copilot suggestion + Appliquer une suggestion Copilot @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + Appliquer le correctif à partir de @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + Avertissement : il est possible que les suggestions IA soient inexactes. @@ -267,6 +267,11 @@ déclaration using asynchrone {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias alias externe diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf index e33829f8faa01..6a1440d66d551 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + Applicare '{0}' Apply Copilot suggestion - Apply Copilot suggestion + Applicare suggerimento di Copilot @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + Applicare correzione da @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + Avviso: i suggerimenti dell'intelligenza artificiale potrebbero non essere accurati. @@ -267,6 +267,11 @@ dichiarazione using asincrona {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias alias extern diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf index aff6049697ee7..11dd8ccbb2e28 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + '{0}' を適用する Apply Copilot suggestion - Apply Copilot suggestion + Copilot の提案を適用する @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + 修正プログラムの適用元 @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + 警告: AI の提案は不正確である可能性があります。 @@ -267,6 +267,11 @@ 非同期の using 宣言 {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias extern エイリアス diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf index 34dd67a3519c7..b73f52d47bf3b 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + ‘{0}’ 적용 Apply Copilot suggestion - Apply Copilot suggestion + Copilot 제안 적용 @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + 다음에서 수정 사항 적용 @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + 경고: AI 제안은 정확하지 않을 수 있습니다. @@ -267,6 +267,11 @@ 비동기 using 선언 {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias extern 별칭 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf index 8dc9e15b0f39e..52f640354543d 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + Zastosuj „{0}” Apply Copilot suggestion - Apply Copilot suggestion + Zastosuj sugestię funkcji Copilot @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + Zastosuj poprawkę z @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + Uwaga: sugestie sztucznej inteligencji mogą być niedokładne. @@ -267,6 +267,11 @@ asynchroniczna deklaracja using {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias alias zewnętrzny diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf index b9e63880cd1ba..7ab6a42188b13 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + Aplicar "{0} Apply Copilot suggestion - Apply Copilot suggestion + Aplicar a sugestão do Copilot @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + Aplicar correção de @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + Aviso: As sugestões de IA podem ser imprecisas. @@ -267,6 +267,11 @@ declaração using assíncrona {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias alias externo diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf index 5681aac9e1314..28e83bb4a9a22 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + Применить "{0}" Apply Copilot suggestion - Apply Copilot suggestion + Применить предложение Copilot @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + Применить исправление из @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + Предупреждение: предложения ИИ могут быть неточными. @@ -267,6 +267,11 @@ асинхронное объявление using {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias внешний псевдоним diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf index 98f5ac0813cea..25484efa105e1 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + '{0}' uygula Apply Copilot suggestion - Apply Copilot suggestion + Copilot önerisini uygula @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + Düzeltmeyi uygulama kaynağı @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + Uyarı: Yapay zeka önerileri yanlış olabilir. @@ -267,6 +267,11 @@ zaman uyumsuz using bildirimi {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias dış diğer ad diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf index 596c0cb3532ca..bef0d35b51ee5 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + 应用“{0}” Apply Copilot suggestion - Apply Copilot suggestion + 应用 Copilot 的建议 @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + 应用来自以下位置的修复 @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + 警告:AI 建议可能不准确。 @@ -267,6 +267,11 @@ 异步 using 声明 {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias 外部别名 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf index f0ac9a3cfb1ac..18a89560a0a31 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf @@ -24,12 +24,12 @@ Apply '{0}' - Apply '{0}' + 套用 '{0}' Apply Copilot suggestion - Apply Copilot suggestion + 套用 Copilot 建議 @@ -69,7 +69,7 @@ Apply fix from - Apply fix from + 套用修正來源 @@ -254,7 +254,7 @@ Warning: AI suggestions might be inaccurate. - Warning: AI suggestions might be inaccurate. + 警告: AI 建議可能不正確。 @@ -267,6 +267,11 @@ 非同步 using 宣告 {Locked="using"} "using" is a C# keyword and should not be localized. + + do-while loop + do-while loop + {Locked="do"}{Locked="while"} "do" and "while" are C# keywords and should not be localized. + extern alias 外部別名 diff --git a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs index f691d31a81708..6a59132c0fd36 100644 --- a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs +++ b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs @@ -21,7 +21,7 @@ public class ConvertConcatenationToInterpolatedStringTests [Fact] public async Task TestMissingOnSimpleString() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -29,15 +29,13 @@ void M() var v = [||]"string"; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact] public async Task TestMissingOnConcatenatedStrings1() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -45,15 +43,13 @@ void M() var v = [||]"string" + "string"; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact] public async Task TestMissingOnConcatenatedStrings2() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -61,15 +57,13 @@ void M() var v = "string" + [||]"string"; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact] public async Task TestMissingOnConcatenatedStrings3() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -77,9 +71,7 @@ void M() var v = "string" + '.' + [||]"string"; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact] @@ -305,7 +297,7 @@ void M() [Fact] public async Task TestMissingWithMixedStringTypes1() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -313,15 +305,13 @@ void M() var v = 1 + [||]@"string" + 2 + "string"; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact] public async Task TestMissingWithMixedStringTypes2() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -329,15 +319,13 @@ void M() var v = 1 + @"string" + 2 + [||]"string"; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact] public async Task TestMissingWithMixedStringTypes3() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -345,9 +333,7 @@ void M() var v = 1 + @"string" + 2 + [||]'\n'; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact] @@ -391,7 +377,7 @@ void M() [Fact] public async Task TestWithOverloadedOperator2() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" public class D { public static int operator +(D d, string s) => 0; @@ -406,9 +392,7 @@ void M() var v = d + [||]"string" + 1; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/16820")] @@ -486,7 +470,7 @@ void M() [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/16820")] public async Task TestWithMultipleStringConcatenations4() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -494,15 +478,13 @@ void M() var v = "A" + 1 + [||]"B" + @"C"; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/20943")] public async Task TestMissingWithDynamic1() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" class C { void M() @@ -511,15 +493,13 @@ void M() string c = [||]"d" + a + "e"; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/20943")] public async Task TestMissingWithDynamic2() { - var code = """ + await VerifyCS.VerifyRefactoringAsync(""" class C { void M() @@ -528,9 +508,7 @@ void M() var x = dynamic.someVal + [||]" $"; } } - """; - - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23536")] @@ -713,7 +691,8 @@ void M() [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/16981")] public async Task TestMissingWithSelectionOnPartOfToBeInterpolatedStringPrefix() { - var code = """ + // see comment in AbstractConvertConcatenationToInterpolatedStringRefactoringProvider:ComputeRefactoringsAsync + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -721,17 +700,15 @@ void M() var v = [|"string" + 1|] + "string"; } } - """; - - // see comment in AbstractConvertConcatenationToInterpolatedStringRefactoringProvider:ComputeRefactoringsAsync - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] [WorkItem("https://github.com/dotnet/roslyn/issues/16981")] public async Task TestMissingWithSelectionOnPartOfToBeInterpolatedStringSuffix() { - var code = """ + // see comment in AbstractConvertConcatenationToInterpolatedStringRefactoringProvider:ComputeRefactoringsAsync + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -739,17 +716,15 @@ void M() var v = "string" + [|1 + "string"|]; } } - """; - - // see comment in AbstractConvertConcatenationToInterpolatedStringRefactoringProvider:ComputeRefactoringsAsync - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] [WorkItem("https://github.com/dotnet/roslyn/issues/16981")] public async Task TestMissingWithSelectionOnMiddlePartOfToBeInterpolatedString() { - var code = """ + // see comment in AbstractConvertConcatenationToInterpolatedStringRefactoringProvider:ComputeRefactoringsAsync + await VerifyCS.VerifyRefactoringAsync(""" public class C { void M() @@ -757,10 +732,7 @@ void M() var v = "a" + [|1 + "string"|] + "b"; } } - """; - - // see comment in AbstractConvertConcatenationToInterpolatedStringRefactoringProvider:ComputeRefactoringsAsync - await VerifyCS.VerifyRefactoringAsync(code, code); + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/16981")] @@ -1036,8 +1008,9 @@ void M() } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40413")] - public async Task TestConcatenationWithConstMember() + public async Task TestConcatenationWithConstMemberCSharp9() { + // lang=c#-test var code = """ class C { @@ -1046,27 +1019,36 @@ class C const string Message = Hello + " " + [||]World; } """; - var fixedCode = """ - class C - { - const string Hello = "Hello"; - const string World = "World"; - const string Message = $"{Hello} {World}"; - } - """; - await new VerifyCS.Test { LanguageVersion = LanguageVersion.CSharp9, TestCode = code, FixedCode = code, }.RunAsync(); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40413")] + public async Task TestConcatenationWithConstMember() + { await new VerifyCS.Test { LanguageVersion = LanguageVersion.Preview, - TestCode = code, - FixedCode = fixedCode, + TestCode = """ + class C + { + const string Hello = "Hello"; + const string World = "World"; + const string Message = Hello + " " + [||]World; + } + """, + FixedCode = """ + class C + { + const string Hello = "Hello"; + const string World = "World"; + const string Message = $"{Hello} {World}"; + } + """, }.RunAsync(); } diff --git a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs index 976bff673fddb..94aec2eaaf94b 100644 --- a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs +++ b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs @@ -86,110 +86,126 @@ static string MakeFormattedParameters(int numberOfParameters) public async Task TestInvocationSubstitution(string before, string after) { await TestInRegularAndScriptAsync( -@$"using System; -using System.Diagnostics; - -class T -{{ - void M() - {{ - [|{before}|]; - }} -}}", -@$"using System; -using System.Diagnostics; - -class T -{{ - void M() - {{ - {after}; - }} -}}"); + $$""" + using System; + using System.Diagnostics; + + class T + { + void M() + { + [|{{before}}|]; + } + } + """, + $$""" + using System; + using System.Diagnostics; + + class T + { + void M() + { + {{after}}; + } + } + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/55053")] public async Task TestMissing_ConsoleWriteLine() { await TestMissingAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var i = 25; - [|Console.WriteLine(GetString(), i)|]; - } + class T + { + void M() + { + var i = 25; + [|Console.WriteLine(GetString(), i)|]; + } - string GetString() => """"; -}"); + string GetString() => ""; + } + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/55053")] public async Task TestMissing_ConsoleWrite() { await TestMissingAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var i = 25; - [|Console.Write(GetString(), i)|]; - } + class T + { + void M() + { + var i = 25; + [|Console.Write(GetString(), i)|]; + } - string GetString() => """"; -}"); + string GetString() => ""; + } + """); } [Fact] public async Task TestItemOrdering() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}{1}{2}"", 1, 2, 3)|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0}{1}{2}", 1, 2, 3)|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{1}{2}{3}""; - } -}"); + class T + { + void M() + { + var a = $"{1}{2}{3}"; + } + } + """); } [Fact] public async Task TestItemOrdering2() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}{2}{1}"", 1, 2, 3)|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0}{2}{1}", 1, 2, 3)|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{1}{3}{2}""; - } -}"); + class T + { + void M() + { + var a = $"{1}{3}{2}"; + } + } + """); } [Fact] @@ -198,84 +214,96 @@ public async Task TestItemOrdering3() // Missing as we have arguments we don't know what to do with here. Likely a bug in user code that needs // fixing first. await TestMissingAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}{0}{0}"", 1, 2, 3)|]; - } -}"); + class T + { + void M() + { + var a = [|string.Format("{0}{0}{0}", 1, 2, 3)|]; + } + } + """); } [Fact] public async Task TestItemOrdering4() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}{1}{2}{0}{1}{2}"", 1, 2, 3)|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0}{1}{2}{0}{1}{2}", 1, 2, 3)|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{1}{2}{3}{1}{2}{3}""; - } -}"); + class T + { + void M() + { + var a = $"{1}{2}{3}{1}{2}{3}"; + } + } + """); } [Fact] public async Task TestNotWithMissingCurly1() { await TestMissingAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""0}{"", 1)|]; - } -}"); + class T + { + void M() + { + var a = [|string.Format("0}{", 1)|]; + } + } + """); } [Fact] public async Task TestNotWithMissingCurly2() { await TestMissingAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""0{"", 1)|]; - } -}"); + class T + { + void M() + { + var a = [|string.Format("0{", 1)|]; + } + } + """); } [Fact] public async Task TestNotWithIncorrectSyntax1() { await TestMissingAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{:0}"", 1)|]; - } -}"); + class T + { + void M() + { + var a = [|string.Format("{:0}", 1)|]; + } + } + """); } [Fact] @@ -283,30 +311,34 @@ public async Task TestNotWithMatchWithFollowingNumber() { // want to make sure that `{1` is not matched as `{1}` await TestMissingAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}{12}"", 1, 2)|]; - } -}"); + class T + { + void M() + { + var a = [|string.Format("{0}{12}", 1, 2)|]; + } + } + """); } [Fact] public async Task TestNotWithStringThatParsesWrongAsInterpolation() { await TestMissingAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}{a +}"", 1)|]; - } -}"); + class T + { + void M() + { + var a = [|string.Format("{0}{a +}", 1)|]; + } + } + """); } [Fact] @@ -314,477 +346,541 @@ public async Task TestItemOutsideRange() { // Missing as the format string refers to parameters that aren't provided. await TestMissingAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{4}{5}{6}"", 1, 2, 3)|]; - } -}"); + class T + { + void M() + { + var a = [|string.Format("{4}{5}{6}", 1, 2, 3)|]; + } + } + """); } [Fact] public async Task TestItemDoNotHaveCast() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}{1}{2}"", 0.5, ""Hello"", 3)|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0}{1}{2}", 0.5, "Hello", 3)|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{0.5}{""Hello""}{3}""; - } -}"); + class T + { + void M() + { + var a = $"{0.5}{"Hello"}{3}"; + } + } + """); } [Fact] public async Task TestItemWithSyntaxErrorDoesHaveCast() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}"", new object)|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0}", new object)|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{(object)new object}""; - } -}"); + class T + { + void M() + { + var a = $"{(object)new object}"; + } + } + """); } [Fact] public async Task TestItemWithoutSyntaxErrorDoesNotHaveCast() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}"", new object())|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0}", new object())|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{new object()}""; - } -}"); + class T + { + void M() + { + var a = $"{new object()}"; + } + } + """); } [Fact] public async Task TestParenthesisAddedForTernaryExpression() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}"", true ? ""Yes"" : ""No"")|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0}", true ? "Yes" : "No")|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{(true ? ""Yes"" : ""No"")}""; - } -}"); + class T + { + void M() + { + var a = $"{(true ? "Yes" : "No")}"; + } + } + """); } [Fact] public async Task TestDoesNotAddDoubleParenthesisForTernaryExpression() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}"", (true ? ""Yes"" : ""No""))|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0}", (true ? "Yes" : "No"))|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{(true ? ""Yes"" : ""No"")}""; - } -}"); + class T + { + void M() + { + var a = $"{(true ? "Yes" : "No")}"; + } + } + """); } [Fact] public async Task TestMultiLineExpression() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format( - ""{0}"", - true ? ""Yes"" : false as object)|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format( + "{0}", + true ? "Yes" : false as object)|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{(true ? ""Yes"" : false as object)}""; - } -}"); + class T + { + void M() + { + var a = $"{(true ? "Yes" : false as object)}"; + } + } + """); } [Fact] public async Task TestFormatSpecifiers() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - Decimal pricePerOunce = 17.36m; - String s = [|String.Format(""The current price is { 0:C2} per ounce."", - pricePerOunce)|]; - } -}", -@"using System; + class T + { + void M() + { + Decimal pricePerOunce = 17.36m; + String s = [|String.Format("The current price is { 0:C2} per ounce.", + pricePerOunce)|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - Decimal pricePerOunce = 17.36m; - String s = $""The current price is {pricePerOunce:C2} per ounce.""; - } -}"); + class T + { + void M() + { + Decimal pricePerOunce = 17.36m; + String s = $"The current price is {pricePerOunce:C2} per ounce."; + } + } + """); } [Fact] public async Task TestFormatSpecifiers2() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - string s = [|String.Format(""It is now {0:d} at {0:t}"", DateTime.Now)|]; - } -}", -@"using System; + class T + { + void M() + { + string s = [|String.Format("It is now {0:d} at {0:t}", DateTime.Now)|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - string s = $""It is now {DateTime.Now:d} at {DateTime.Now:t}""; - } -}"); + class T + { + void M() + { + string s = $"It is now {DateTime.Now:d} at {DateTime.Now:t}"; + } + } + """); } [Fact] public async Task TestFormatSpecifiers3() { await TestInRegularAndScriptAsync( -@"using System; -class T -{ - void M() - { - int[] years = { 2013, 2014, 2015 }; - int[] population = { 1025632, 1105967, 1148203 }; - String s = String.Format(""{0,6} {1,15}\n\n"", ""Year"", ""Population""); - for (int index = 0; index < years.Length; index++) - s += [|String.Format(""{0,6} {1,15:N0}\n"", - years[index], population[index])|]; - } -}", -@"using System; -class T -{ - void M() - { - int[] years = { 2013, 2014, 2015 }; - int[] population = { 1025632, 1105967, 1148203 }; - String s = String.Format(""{0,6} {1,15}\n\n"", ""Year"", ""Population""); - for (int index = 0; index < years.Length; index++) - s += $""{years[index],6} {population[index],15:N0}\n""; - } -}"); + """ + using System; + class T + { + void M() + { + int[] years = { 2013, 2014, 2015 }; + int[] population = { 1025632, 1105967, 1148203 }; + String s = String.Format("{0,6} {1,15}\n\n", "Year", "Population"); + for (int index = 0; index < years.Length; index++) + s += [|String.Format("{0,6} {1,15:N0}\n", + years[index], population[index])|]; + } + } + """, + """ + using System; + class T + { + void M() + { + int[] years = { 2013, 2014, 2015 }; + int[] population = { 1025632, 1105967, 1148203 }; + String s = String.Format("{0,6} {1,15}\n\n", "Year", "Population"); + for (int index = 0; index < years.Length; index++) + s += $"{years[index],6} {population[index],15:N0}\n"; + } + } + """); } [Fact] public async Task TestFormatSpecifiers4() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|String.Format(""{ 0,-10:C}"", 126347.89m)|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|String.Format("{ 0,-10:C}", 126347.89m)|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{126347.89m,-10:C}""; - } -}"); + class T + { + void M() + { + var a = $"{126347.89m,-10:C}"; + } + } + """); } [Fact] public async Task TestFormatSpecifiers5() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -public class T -{ - public static void M() - { - Tuple[] cities = { - Tuple.Create(""Los Angeles"", new DateTime(1940, 1, 1), 1504277, - new DateTime(1950, 1, 1), 1970358), - Tuple.Create(""New York"", new DateTime(1940, 1, 1), 7454995, - new DateTime(1950, 1, 1), 7891957), - Tuple.Create(""Chicago"", new DateTime(1940, 1, 1), 3396808, - new DateTime(1950, 1, 1), 3620962), - Tuple.Create(""Detroit"", new DateTime(1940, 1, 1), 1623452, - new DateTime(1950, 1, 1), 1849568) - }; - string output; - foreach (var city in cities) - { - output = [|String.Format(""{0,-12}{1,8:yyyy}{2,12:N0}{3,8:yyyy}{4,12:N0}{5,14:P1}"", - city.Item1, city.Item2, city.Item3, city.Item4, city.Item5, - (city.Item5 - city.Item3) / (double)city.Item3)|]; - } - } -}", -@"using System; + public class T + { + public static void M() + { + Tuple[] cities = { + Tuple.Create("Los Angeles", new DateTime(1940, 1, 1), 1504277, + new DateTime(1950, 1, 1), 1970358), + Tuple.Create("New York", new DateTime(1940, 1, 1), 7454995, + new DateTime(1950, 1, 1), 7891957), + Tuple.Create("Chicago", new DateTime(1940, 1, 1), 3396808, + new DateTime(1950, 1, 1), 3620962), + Tuple.Create("Detroit", new DateTime(1940, 1, 1), 1623452, + new DateTime(1950, 1, 1), 1849568) + }; + string output; + foreach (var city in cities) + { + output = [|String.Format("{0,-12}{1,8:yyyy}{2,12:N0}{3,8:yyyy}{4,12:N0}{5,14:P1}", + city.Item1, city.Item2, city.Item3, city.Item4, city.Item5, + (city.Item5 - city.Item3) / (double)city.Item3)|]; + } + } + } + """, + """ + using System; -public class T -{ - public static void M() - { - Tuple[] cities = { - Tuple.Create(""Los Angeles"", new DateTime(1940, 1, 1), 1504277, - new DateTime(1950, 1, 1), 1970358), - Tuple.Create(""New York"", new DateTime(1940, 1, 1), 7454995, - new DateTime(1950, 1, 1), 7891957), - Tuple.Create(""Chicago"", new DateTime(1940, 1, 1), 3396808, - new DateTime(1950, 1, 1), 3620962), - Tuple.Create(""Detroit"", new DateTime(1940, 1, 1), 1623452, - new DateTime(1950, 1, 1), 1849568) - }; - string output; - foreach (var city in cities) - { - output = $""{city.Item1,-12}{city.Item2,8:yyyy}{city.Item3,12:N0}{city.Item4,8:yyyy}{city.Item5,12:N0}{(city.Item5 - city.Item3) / (double)city.Item3,14:P1}""; - } - } -}"); + public class T + { + public static void M() + { + Tuple[] cities = { + Tuple.Create("Los Angeles", new DateTime(1940, 1, 1), 1504277, + new DateTime(1950, 1, 1), 1970358), + Tuple.Create("New York", new DateTime(1940, 1, 1), 7454995, + new DateTime(1950, 1, 1), 7891957), + Tuple.Create("Chicago", new DateTime(1940, 1, 1), 3396808, + new DateTime(1950, 1, 1), 3620962), + Tuple.Create("Detroit", new DateTime(1940, 1, 1), 1623452, + new DateTime(1950, 1, 1), 1849568) + }; + string output; + foreach (var city in cities) + { + output = $"{city.Item1,-12}{city.Item2,8:yyyy}{city.Item3,12:N0}{city.Item4,8:yyyy}{city.Item5,12:N0}{(city.Item5 - city.Item3) / (double)city.Item3,14:P1}"; + } + } + } + """); } [Fact] public async Task TestFormatSpecifiers6() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -public class T -{ - public static void M() - { - short[] values = { - Int16.MinValue, - -27, - 0, - 1042, - Int16.MaxValue - }; - foreach (short value in values) - { - string formatString = [|String.Format(""{0,10:G}: {0,10:X}"", value)|]; - } - } -}", -@"using System; + public class T + { + public static void M() + { + short[] values = { + Int16.MinValue, + -27, + 0, + 1042, + Int16.MaxValue + }; + foreach (short value in values) + { + string formatString = [|String.Format("{0,10:G}: {0,10:X}", value)|]; + } + } + } + """, + """ + using System; -public class T -{ - public static void M() - { - short[] values = { - Int16.MinValue, - -27, - 0, - 1042, - Int16.MaxValue - }; - foreach (short value in values) - { - string formatString = $""{value,10:G}: {value,10:X}""; - } - } -}"); + public class T + { + public static void M() + { + short[] values = { + Int16.MinValue, + -27, + 0, + 1042, + Int16.MaxValue + }; + foreach (short value in values) + { + string formatString = $"{value,10:G}: {value,10:X}"; + } + } + } + """); } [Fact] public async Task TestVerbatimStringLiteral() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -public class T -{ - public static void M() - { - int value1 = 16932; - int value2 = 15421; - string result = [|string.Format(@"" - {0,10} ({0,8:X8}) -And {1,10} ({1,8:X8}) - = {2,10} ({2,8:X8})"", - value1, value2, value1 & value2)|]; - } -}", -@"using System; + public class T + { + public static void M() + { + int value1 = 16932; + int value2 = 15421; + string result = [|string.Format(@" + {0,10} ({0,8:X8}) + And {1,10} ({1,8:X8}) + = {2,10} ({2,8:X8})", + value1, value2, value1 & value2)|]; + } + } + """, + """ + using System; -public class T -{ - public static void M() - { - int value1 = 16932; - int value2 = 15421; - string result = $@"" - {value1,10} ({value1,8:X8}) -And {value2,10} ({value2,8:X8}) - = {value1 & value2,10} ({value1 & value2,8:X8})""; - } -}"); + public class T + { + public static void M() + { + int value1 = 16932; + int value2 = 15421; + string result = $@" + {value1,10} ({value1,8:X8}) + And {value2,10} ({value2,8:X8}) + = {value1 & value2,10} ({value1 & value2,8:X8})"; + } + } + """); } [Fact] public async Task TestFormatWithParams() { await TestMissingInRegularAndScriptAsync( -@"using System; + """ + using System; -public class T -{ - public static void M() - { - DateTime date1 = new DateTime(2009, 7, 1); - TimeSpan hiTime = new TimeSpan(14, 17, 32); - decimal hiTemp = 62.1m; - TimeSpan loTime = new TimeSpan(3, 16, 10); - decimal loTemp = 54.8m; - string result = [|String.Format(@""Temperature on {0:d}: - {1,11}: {2} degrees (hi) - {3,11}: {4} degrees (lo)"", - new object[] { date1, hiTime, hiTemp, loTime, loTemp })|]; - } -}"); + public class T + { + public static void M() + { + DateTime date1 = new DateTime(2009, 7, 1); + TimeSpan hiTime = new TimeSpan(14, 17, 32); + decimal hiTemp = 62.1m; + TimeSpan loTime = new TimeSpan(3, 16, 10); + decimal loTemp = 54.8m; + string result = [|String.Format(@"Temperature on {0:d}: + {1,11}: {2} degrees (hi) + {3,11}: {4} degrees (lo)", + new object[] { date1, hiTime, hiTemp, loTime, loTemp })|]; + } + } + """); } [Fact] public async Task TestInvalidInteger() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -public class T -{ - public static void M() - { - string result = [|String.Format(""{0L}"", 5)|]; - } -}", -@"using System; + public class T + { + public static void M() + { + string result = [|String.Format("{0L}", 5)|]; + } + } + """, + """ + using System; -public class T -{ - public static void M() - { - string result = $""{5}""; - } -}"); + public class T + { + public static void M() + { + string result = $"{5}"; + } + } + """); } [Fact] public async Task TestOutVariableDeclaration_01() { await TestMissingInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0}"", out int x)|]; - } -}"); + class T + { + void M() + { + var a = [|string.Format("{0}", out int x)|]; + } + } + """); } [Fact] public async Task TestOutVariableDeclaration_02() { await TestMissingInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(out string x, 1)|]; - } -}"); + class T + { + void M() + { + var a = [|string.Format(out string x, 1)|]; + } + } + """); } [Fact] @@ -793,263 +889,303 @@ public async Task TestFormatWithNamedArguments1() // Missing as this scenario is too esoteric. I was not able to find any examples of code that reorders and // names thigns like this with format strings. await TestMissingAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(arg0: ""test"", arg1: ""also"", format: ""This {0} {1} works"")|]; - } -}"); + class T + { + void M() + { + var a = [|string.Format(arg0: "test", arg1: "also", format: "This {0} {1} works")|]; + } + } + """); } [Fact] public async Task TestFormatWithNamedArguments2() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""This {0} {1} works"", arg1: ""also"", arg0: ""test"")|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("This {0} {1} works", arg1: "also", arg0: "test")|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""This {""test""} {""also""} works""; - } -}"); + class T + { + void M() + { + var a = $"This {"test"} {"also"} works"; + } + } + """); } [Fact] public async Task TestFormatWithNamedArguments3() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0} {1} {2}"", ""10"", arg1: ""11"", arg2: ""12"" )|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0} {1} {2}", "10", arg1: "11", arg2: "12" )|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{""10""} {""11""} {""12""}""; - } -}"); + class T + { + void M() + { + var a = $"{"10"} {"11"} {"12"}"; + } + } + """); } [Fact] public async Task TestFormatWithNamedArguments4() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0} {1} {2}"", ""10"", arg2: ""12"", arg1: ""11"")|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0} {1} {2}", "10", arg2: "12", arg1: "11")|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{""10""} {""11""} {""12""}""; - } -}"); + class T + { + void M() + { + var a = $"{"10"} {"11"} {"12"}"; + } + } + """); } [Fact] public async Task TestFormatWithNamedArguments5() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = [|string.Format(""{0} {1} {2} {3}"", ""10"", arg1: ""11"", arg2: ""12"")|]; - } -}", -@"using System; + class T + { + void M() + { + var a = [|string.Format("{0} {1} {2} {3}", "10", arg1: "11", arg2: "12")|]; + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{""10""} {""11""} {""12""} {3}""; - } -}"); + class T + { + void M() + { + var a = $"{"10"} {"11"} {"12"} {3}"; + } + } + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] public async Task TestOnlyArgumentSelection1() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = string.Format([|""{0}""|], 1); - } -}", -@"using System; + class T + { + void M() + { + var a = string.Format([|"{0}"|], 1); + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{1}""; - } -}"); + class T + { + void M() + { + var a = $"{1}"; + } + } + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] public async Task TestOnlyArgumentSelection2() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = string.Format(""{0}"", [|1|]); - } -}", -@"using System; + class T + { + void M() + { + var a = string.Format("{0}", [|1|]); + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{1}""; - } -}"); + class T + { + void M() + { + var a = $"{1}"; + } + } + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] public async Task TestArgumentsSelection2() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -class T -{ - void M() - { - var a = string.Format([|""{0}"", 1|]); - } -}", -@"using System; + class T + { + void M() + { + var a = string.Format([|"{0}", 1|]); + } + } + """, + """ + using System; -class T -{ - void M() - { - var a = $""{1}""; - } -}"); + class T + { + void M() + { + var a = $"{1}"; + } + } + """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/61346")] public async Task TestNoCastToObjectWhenNullableEnabled() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -#nullable enable + #nullable enable -class T -{ - void M() - { - var a = string.Format([|""{0}"", 1|]); - } -}", -@"using System; + class T + { + void M() + { + var a = string.Format([|"{0}", 1|]); + } + } + """, + """ + using System; -#nullable enable + #nullable enable -class T -{ - void M() - { - var a = $""{1}""; - } -}"); + class T + { + void M() + { + var a = $"{1}"; + } + } + """); } [Fact, WorkItem(1756068, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1756068")] public async Task TestArbitraryAPI() { await TestInRegularAndScriptAsync( -@"using System; + """ + using System; -interface ILogger -{ - public void Log(string s) { } - public void Log(string s, object arg1) { } -} + interface ILogger + { + public void Log(string s) { } + public void Log(string s, object arg1) { } + } -class T -{ - void M(ILogger logger) - { - [|logger.Log(""{0}"", 5)|]; - } -}", -@"using System; + class T + { + void M(ILogger logger) + { + [|logger.Log("{0}", 5)|]; + } + } + """, + """ + using System; -interface ILogger -{ - public void Log(string s) { } - public void Log(string s, object arg1) { } -} + interface ILogger + { + public void Log(string s) { } + public void Log(string s, object arg1) { } + } -class T -{ - void M(ILogger logger) - { - logger.Log($""{5}""); - } -}"); + class T + { + void M(ILogger logger) + { + logger.Log($"{5}"); + } + } + """); } [Fact, WorkItem(61346, "https://github.com/dotnet/roslyn/issues/61346")] public async Task TestNotWithExplicitCultureInfo() { await TestMissingInRegularAndScriptAsync( -@"using System; -using System.Globalization; + """ + using System; + using System.Globalization; -class T -{ - void M() - { - var a = string.Format(CultureInfo.InvariantCulture, [|""{0}"", 1|]); - } -}"); + class T + { + void M() + { + var a = string.Format(CultureInfo.InvariantCulture, [|"{0}", 1|]); + } + } + """); } } } diff --git a/src/Features/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs b/src/Features/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs index 39e036613e679..f48f6e7b8cdaa 100644 --- a/src/Features/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs +++ b/src/Features/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs @@ -170,7 +170,7 @@ protected override ImmutableArray UnsupportedDiagnosticIds } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } } diff --git a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionAllCodeTests.cs b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionAllCodeTests.cs index c386b7144eb9e..f983bd0388233 100644 --- a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionAllCodeTests.cs +++ b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionAllCodeTests.cs @@ -24,9 +24,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.Suppression public class CSharpSuppressionAllCodeTests : AbstractSuppressionAllCodeTests { private static readonly TestComposition s_compositionWithMockDiagnosticUpdateSourceRegistrationService = FeaturesTestCompositions.Features - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)) - .AddAssemblies(typeof(DiagnosticService).Assembly); + .AddAssemblies(typeof(DiagnosticAnalyzerService).Assembly); protected override TestWorkspace CreateWorkspaceFromFile(string definition, ParseOptions parseOptions) => TestWorkspace.CreateCSharp(definition, (CSharpParseOptions)parseOptions, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService); diff --git a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs index 708742131ab72..83d05ebe6d16f 100644 --- a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs +++ b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs @@ -448,7 +448,6 @@ void Method() var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(new CSharpCompilerDiagnosticAnalyzer())); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); - Assert.IsType(workspace.ExportProvider.GetExportedValue()); var diagnosticService = Assert.IsType(workspace.ExportProvider.GetExportedValue()); var incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace); var suppressionProvider = CreateDiagnosticProviderAndFixer(workspace).Item2; @@ -456,9 +455,9 @@ void Method() new CodeChangeProviderMetadata("SuppressionProvider", languages: [LanguageNames.CSharp])); var fixService = new CodeFixService( diagnosticService, - SpecializedCollections.EmptyEnumerable>(), - SpecializedCollections.EmptyEnumerable>(), - SpecializedCollections.SingletonEnumerable(suppressionProviderFactory)); + loggers: [], + fixers: [], + [suppressionProviderFactory]); var document = GetDocumentAndSelectSpan(workspace, out var span); var diagnostics = await diagnosticService.GetDiagnosticsForSpanAsync(document, span, CancellationToken.None); Assert.Equal(2, diagnostics.Where(d => d.Id == "CS0219").Count()); diff --git a/src/Features/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/Features/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 3ed08b00727ff..78408bd6fff22 100644 --- a/src/Features/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -10453,7 +10453,7 @@ static IEnumerable F() // should not contain RUDE_EDIT_INSERT_AROUND edits.VerifySemanticDiagnostics( active, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -10484,7 +10484,7 @@ static IEnumerable F() edits.VerifySemanticDiagnostics( active, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -10667,7 +10667,7 @@ static async Task F() // should not contain RUDE_EDIT_INSERT_AROUND edits.VerifySemanticDiagnostics( active, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -10758,7 +10758,7 @@ static async Task F() edits.VerifySemanticDiagnostics( active, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -10786,7 +10786,7 @@ static async void F() var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] diff --git a/src/Features/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/Features/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index c6d22fe6e7ece..8c76e4a890056 100644 --- a/src/Features/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -119,12 +119,13 @@ private static async Task AnalyzeDocumentAsync( Project oldProject, Document newDocument, ActiveStatementsMap activeStatementMap = null, - EditAndContinueCapabilities capabilities = EditAndContinueTestHelpers.Net5RuntimeCapabilities) + EditAndContinueCapabilities capabilities = EditAndContinueTestHelpers.Net5RuntimeCapabilities, + ImmutableArray newActiveStatementSpans = default) { var analyzer = new CSharpEditAndContinueAnalyzer(); var baseActiveStatements = AsyncLazy.Create(activeStatementMap ?? ActiveStatementsMap.Empty); var lazyCapabilities = AsyncLazy.Create(capabilities); - return await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, [], lazyCapabilities, CancellationToken.None); + return await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, newActiveStatementSpans.NullToEmpty(), lazyCapabilities, CancellationToken.None); } #endregion @@ -319,7 +320,7 @@ public static void Main() { KeyValuePairUtil.Create(newDocument.FilePath, ImmutableArray.Create( new ActiveStatement( - ordinal: 0, + new ActiveStatementId(0), ActiveStatementFlags.LeafFrame, new SourceFileSpan(newDocument.FilePath, oldStatementSpan), instructionId: default))) diff --git a/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs b/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index 7d9e4f7c66ec5..9e42352af2e27 100644 --- a/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs +++ b/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs @@ -27,7 +27,7 @@ internal static void VerifyLineEdits( VerifyLineEdits( editScript, - new[] { new SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, lineEdits.ToImmutableArray()) }, + new[] { new SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, [.. lineEdits]) }, semanticEdits, diagnostics, capabilities); diff --git a/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs index e060c80f69f0f..aa466cf1e9c6f 100644 --- a/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -6569,7 +6569,7 @@ public void F() capabilities: EditAndContinueCapabilities.Baseline); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } #endregion @@ -9615,7 +9615,7 @@ async Task WaitAsync() capabilities: EditAndContinueCapabilities.Baseline); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -9649,7 +9649,7 @@ IEnumerable Iter() capabilities: EditAndContinueCapabilities.Baseline); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -11484,7 +11484,7 @@ static IEnumerable F() edits.VerifySemanticDiagnostics( targetFrameworks: [TargetFramework.Mscorlib40AndSystemCore], - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } #endregion @@ -12079,7 +12079,7 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -12431,7 +12431,7 @@ static async Task F() edits.VerifySemanticDiagnostics( targetFrameworks: [TargetFramework.MinimalAsync], - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] diff --git a/src/Features/CSharpTest/EditAndContinue/SyntaxComparerTests.cs b/src/Features/CSharpTest/EditAndContinue/SyntaxComparerTests.cs index b2fcacdfb43b5..93c549dd3de2a 100644 --- a/src/Features/CSharpTest/EditAndContinue/SyntaxComparerTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/SyntaxComparerTests.cs @@ -14,6 +14,8 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { + using static CSharpSyntaxTokens; + [UseExportProvider] public class SyntaxComparerTests { @@ -56,8 +58,8 @@ public void GetSequenceEdits2() public void GetSequenceEdits3() { var edits = SyntaxComparer.GetSequenceEdits( - [SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.AsyncKeyword)], - [SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.AsyncKeyword)]); + [PublicKeyword, StaticKeyword, AsyncKeyword], + [StaticKeyword, PublicKeyword, AsyncKeyword]); AssertEx.Equal( [ @@ -72,8 +74,8 @@ public void GetSequenceEdits3() public void GetSequenceEdits4() { var edits = SyntaxComparer.GetSequenceEdits( - ImmutableArray.Create(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.AsyncKeyword)), - ImmutableArray.Create(SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.AsyncKeyword))); + ImmutableArray.Create(PublicKeyword, StaticKeyword, AsyncKeyword), + ImmutableArray.Create(StaticKeyword, PublicKeyword, AsyncKeyword)); AssertEx.Equal( [ @@ -98,8 +100,8 @@ public void ComputeDistance_Nodes() public void ComputeDistance_Tokens() { var distance = SyntaxComparer.ComputeDistance( - [SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.AsyncKeyword)], - [SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.AsyncKeyword)]); + [PublicKeyword, StaticKeyword, AsyncKeyword], + [StaticKeyword, PublicKeyword, AsyncKeyword]); Assert.Equal(0.33, Math.Round(distance, 2)); } @@ -149,9 +151,9 @@ public void ComputeDistance_Null() [Fact] public void ComputeDistance_LongSequences() { - var t1 = SyntaxFactory.Token(SyntaxKind.PublicKeyword); - var t2 = SyntaxFactory.Token(SyntaxKind.PrivateKeyword); - var t3 = SyntaxFactory.Token(SyntaxKind.ProtectedKeyword); + var t1 = PublicKeyword; + var t2 = PrivateKeyword; + var t3 = ProtectedKeyword; var distance = SyntaxComparer.ComputeDistance( Enumerable.Range(0, 10000).Select(i => i < 2000 ? t1 : t2), diff --git a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 1eac532fdd3bb..9df9b818343b7 100644 --- a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -1968,70 +1968,132 @@ public void Struct_Modifiers_Partial_InsertDelete(string modifier) } [Fact] - public void Class_ImplementingInterface_Add() + public void Class_ImplementingInterface_Add_Implicit_NonVirtual() { - var src1 = @" -using System; + var src1 = """ + interface I + { + void F(); + } + """; -public interface ISample -{ - string Get(); -} + var src2 = """ + interface I + { + void F(); + } -public interface IConflict -{ - string Get(); -} + class C : I + { + public void F() {} + } + """; -public class BaseClass : ISample -{ - public virtual string Get() => string.Empty; -} -"; - var src2 = @" -using System; + var edits = GetTopEdits(src1, src2); + edits.VerifySemantics( + [SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C"))], + capabilities: EditAndContinueCapabilities.NewTypeDefinition); + } -public interface ISample -{ - string Get(); -} + [Fact] + public void Class_ImplementingInterface_Add_Implicit_Virtual() + { + var src1 = """ + interface I + { + void F(); + } + """; -public interface IConflict -{ - string Get(); -} + var src2 = """ + interface I + { + void F(); + } -public class BaseClass : ISample -{ - public virtual string Get() => string.Empty; -} + class C : I + { + public virtual void F() {} + } + """; -public class SubClass : BaseClass, IConflict -{ - public override string Get() => string.Empty; + var edits = GetTopEdits(src1, src2); + edits.VerifySemantics( + [SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C"))], + capabilities: EditAndContinueCapabilities.NewTypeDefinition); + } - string IConflict.Get() => String.Empty; -} -"; + [Fact] + public void Class_ImplementingInterface_Add_Implicit_Override() + { + var src1 = """ + interface I + { + void F(); + } + + class C : I + { + public virtual void F() {} + } + """; + + var src2 = """ + interface I + { + void F(); + } + + class C : I + { + public virtual void F() {} + } + + class D : C + { + public override void F() {} + } + """; var edits = GetTopEdits(src1, src2); + edits.VerifySemantics( + [SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("D"))], + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); + } - edits.VerifyEdits( - @"Insert [public class SubClass : BaseClass, IConflict -{ - public override string Get() => string.Empty; + [Theory] + [InlineData("void F();", "void I.F() {}")] + [InlineData("int F { get; }", "int I.F { get; }")] + [InlineData("event System.Action F;", "event System.Action I.F { add {} remove {} }")] + public void Class_ImplementingInterface_Add_Explicit_NonVirtual(string memberDef, string explicitImpl) + { + var src1 = $$""" + interface I + { + {{memberDef}} + } + """; - string IConflict.Get() => String.Empty; -}]@219", - "Insert [: BaseClass, IConflict]@241", - "Insert [public override string Get() => string.Empty;]@272", - "Insert [string IConflict.Get() => String.Empty;]@325", - "Insert [()]@298", - "Insert [()]@345"); + var src2 = $$""" + interface I + { + {{memberDef}} + } + + class C : I + { + {{explicitImpl}} + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + [SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C"))], + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); - // Here we add a class implementing an interface and a method inside it with explicit interface specifier. - // We want to be sure that adding the method will not tirgger a rude edit as it happens if adding a single method with explicit interface specifier. edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "class C", GetResource("class"))], capabilities: EditAndContinueCapabilities.NewTypeDefinition); } @@ -2312,15 +2374,28 @@ void F() {} public void Type_Generic_InsertMembers_Reloadable() { var src1 = ReloadableAttributeSrc + @" +interface IExplicit +{ + void F() {} +} + [CreateNewOnMetadataUpdate] -class C +class C : IExplicit { + void IExplicit.F() {} } "; var src2 = ReloadableAttributeSrc + @" +interface IExplicit +{ + void F() {} +} + [CreateNewOnMetadataUpdate] -class C +class C : IExplicit { + void IExplicit.F() {} + void M() {} int P1 { get; set; } int P2 { get => 1; set {} } @@ -2337,6 +2412,10 @@ class D {} var edits = GetTopEdits(src1, src2); edits.VerifySemantics( [SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C"))], + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, "void M()", "CreateNewOnMetadataUpdateAttribute")], capabilities: EditAndContinueCapabilities.NewTypeDefinition); } @@ -3679,7 +3758,7 @@ public void Record_Property_Delete_ReplacingCustomWithSynthesized_WithBodyAndMet expectedEdits.Add(SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); edits.VerifySemantics( - expectedEdits.ToArray(), + [.. expectedEdits], capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); } @@ -7534,7 +7613,7 @@ public async Task WaitAsync() }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); } @@ -9667,7 +9746,7 @@ public void MethodUpdate_AddYieldReturn() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); } diff --git a/src/Features/CSharpTest/EnableNullable/EnableNullableTests.cs b/src/Features/CSharpTest/EnableNullable/EnableNullableTests.cs index 2d165b775dc31..55ab94f4c6afa 100644 --- a/src/Features/CSharpTest/EnableNullable/EnableNullableTests.cs +++ b/src/Features/CSharpTest/EnableNullable/EnableNullableTests.cs @@ -520,7 +520,7 @@ class Example }, ExpectedDiagnostics = { - // /0/Test3.cs(7,10): error CS8618: Non-nullable field 'value' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // /0/Test3.cs(7,10): error CS8618: Non-nullable field 'value' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. DiagnosticResult.CompilerError("CS8618").WithSpan("/0/Test3.cs", 7, 10, 7, 15).WithSpan("/0/Test3.cs", 7, 10, 7, 15).WithArguments("field", "value"), }, }, diff --git a/src/Features/CSharpTest/ExtractClass/ExtractClassTests.cs b/src/Features/CSharpTest/ExtractClass/ExtractClassTests.cs index dcbb359753ee7..38825daa3ee98 100644 --- a/src/Features/CSharpTest/ExtractClass/ExtractClassTests.cs +++ b/src/Features/CSharpTest/ExtractClass/ExtractClassTests.cs @@ -45,7 +45,7 @@ protected override IEnumerable GetCodeRefactoringProvid FileName = FileName }; - return SpecializedCollections.SingletonEnumerable(new CSharpExtractClassCodeRefactoringProvider(service)); + return [new CSharpExtractClassCodeRefactoringProvider(service)]; } protected override Task CreateWorkspaceImplAsync() diff --git a/src/Features/CSharpTest/Microsoft.CodeAnalysis.CSharp.Features.UnitTests.csproj b/src/Features/CSharpTest/Microsoft.CodeAnalysis.CSharp.Features.UnitTests.csproj index ac384219120d4..801b6a5a0c0e4 100644 --- a/src/Features/CSharpTest/Microsoft.CodeAnalysis.CSharp.Features.UnitTests.csproj +++ b/src/Features/CSharpTest/Microsoft.CodeAnalysis.CSharp.Features.UnitTests.csproj @@ -13,8 +13,30 @@ + + + + + SemanticSearch_RefAssemblies + TargetFramework=$(NetRoslyn) + + + + + + + + + + + + + diff --git a/src/Features/CSharpTest/SemanticSearch/CSharpSemanticSearchServiceTests.cs b/src/Features/CSharpTest/SemanticSearch/CSharpSemanticSearchServiceTests.cs new file mode 100644 index 0000000000000..02d2a79a8fc74 --- /dev/null +++ b/src/Features/CSharpTest/SemanticSearch/CSharpSemanticSearchServiceTests.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.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.SemanticSearch; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SemanticSearch; + +[UseExportProvider] +public sealed class CSharpSemanticSearchServiceTests +{ + private static readonly string s_referenceAssembliesDir = Path.Combine(Path.GetDirectoryName(typeof(CSharpSemanticSearchServiceTests).Assembly.Location!)!, "SemanticSearchRefs"); + private static readonly char[] s_lineBreaks = ['\r', '\n']; + + private static string Inspect(DefinitionItem def) + => string.Join("", def.DisplayParts.Select(p => p.Text)); + + private static string InspectLine(int position, string text) + { + var lineStart = text.LastIndexOfAny(s_lineBreaks, position, position) + 1; + var lineEnd = text.IndexOfAny(s_lineBreaks, position); + if (lineEnd < 0) + { + lineEnd = text.Length; + } + + return text[lineStart..lineEnd].Trim(); + } + + private static string Inspect(UserCodeExceptionInfo info, string query) + => $"{info.ProjectDisplayName}: {info.Span} '{InspectLine(info.Span.Start, query)}': {info.TypeName.JoinText()}: '{info.Message}'"; + + [ConditionalFact(typeof(CoreClrOnly))] + public async Task SimpleQuery() + { + using var workspace = TestWorkspace.Create(""" + + + + namespace N + { + public partial class C + { + public void VisibleMethod() { } + } + } + + + + """, composition: FeaturesTestCompositions.Features); + + var solution = workspace.CurrentSolution; + + var service = solution.Services.GetRequiredLanguageService(LanguageNames.CSharp); + + var query = """ + static IEnumerable Find(Compilation compilation) + { + return compilation.GlobalNamespace.GetMembers("N"); + } + """; + + var results = new List(); + var observer = new MockSemanticSearchResultsObserver() { OnDefinitionFoundImpl = results.Add }; + var traceSource = new TraceSource("test"); + + var options = workspace.GlobalOptions.GetClassificationOptionsProvider(); + var result = await service.ExecuteQueryAsync(solution, query, s_referenceAssembliesDir, observer, options, traceSource, CancellationToken.None); + + Assert.Null(result.ErrorMessage); + AssertEx.Equal(new[] { "namespace N" }, results.Select(Inspect)); + } + + [ConditionalFact(typeof(CoreClrOnly))] + public async Task ForcedCancellation() + { + using var workspace = TestWorkspace.Create(""" + + + + public class C + { + } + + + + """, composition: FeaturesTestCompositions.Features); + + var solution = workspace.CurrentSolution; + + var service = solution.Services.GetRequiredLanguageService(LanguageNames.CSharp); + + var query = """ + static IEnumerable Find(Compilation compilation) + { + yield return compilation.GlobalNamespace.GetMembers("C").First(); + + while (true) + { + + } + } + """; + + var cancellationSource = new CancellationTokenSource(); + var exceptions = new List(); + + var observer = new MockSemanticSearchResultsObserver() + { + // cancel on first result: + OnDefinitionFoundImpl = _ => cancellationSource.Cancel(), + OnUserCodeExceptionImpl = exceptions.Add + }; + + var traceSource = new TraceSource("test"); + var options = workspace.GlobalOptions.GetClassificationOptionsProvider(); + + await Assert.ThrowsAsync( + () => service.ExecuteQueryAsync(solution, query, s_referenceAssembliesDir, observer, options, traceSource, cancellationSource.Token)); + + Assert.Empty(exceptions); + } + + [ConditionalFact(typeof(CoreClrOnly))] + public async Task StackOverflow() + { + using var workspace = TestWorkspace.Create(""" + + + + public class C + { + } + + + + """, composition: FeaturesTestCompositions.Features); + + var solution = workspace.CurrentSolution; + + var service = solution.Services.GetRequiredLanguageService(LanguageNames.CSharp); + + var query = """ + static IEnumerable Find(Compilation compilation) + { + yield return compilation.GlobalNamespace.GetMembers("C").First(); + F(0); + void F(long x) + { + F(x + 1); + } + } + """; + + var exceptions = new List(); + var observer = new MockSemanticSearchResultsObserver() + { + OnUserCodeExceptionImpl = exceptions.Add + }; + + var traceSource = new TraceSource("test"); + var options = workspace.GlobalOptions.GetClassificationOptionsProvider(); + + var result = await service.ExecuteQueryAsync(solution, query, s_referenceAssembliesDir, observer, options, traceSource, CancellationToken.None); + var expectedMessage = new InsufficientExecutionStackException().Message; + AssertEx.Equal(string.Format(FeaturesResources.Semantic_search_query_terminated_with_exception, "CSharpAssembly1", expectedMessage), result.ErrorMessage); + + var exception = exceptions.Single(); + AssertEx.Equal($"CSharpAssembly1: [179..179) 'F(x + 1);': InsufficientExecutionStackException: '{expectedMessage}'", Inspect(exception, query)); + + AssertEx.Equal( + " ..." + Environment.NewLine + + string.Join(Environment.NewLine, Enumerable.Repeat($" at Program.<
$>g__F|0_1(Int64 x) in {FeaturesResources.Query}:line 7", 31)) + Environment.NewLine + + $" at Program.<
$>g__Find|0_0(Compilation compilation)+MoveNext() in Query:line 4" + Environment.NewLine, + exception.StackTrace.JoinText()); + } + + [ConditionalFact(typeof(CoreClrOnly))] + public async Task Exception() + { + using var workspace = TestWorkspace.Create(""" + + + + public class C + { + } + + + + """, composition: FeaturesTestCompositions.Features); + + var solution = workspace.CurrentSolution; + + var service = solution.Services.GetRequiredLanguageService(LanguageNames.CSharp); + + var query = """ + static IEnumerable Find(Compilation compilation) + { + return new[] { (ISymbol)null }.Select(x => + { + return F(x); + }); + } + + static ISymbol F(ISymbol s) + { + var x = s.ToString(); + return s; + } + """; + + var exceptions = new List(); + var observer = new MockSemanticSearchResultsObserver() + { + OnUserCodeExceptionImpl = exceptions.Add + }; + + var traceSource = new TraceSource("test"); + var options = workspace.GlobalOptions.GetClassificationOptionsProvider(); + + var result = await service.ExecuteQueryAsync(solution, query, s_referenceAssembliesDir, observer, options, traceSource, CancellationToken.None); + var expectedMessage = new NullReferenceException().Message; + AssertEx.Equal(string.Format(FeaturesResources.Semantic_search_query_terminated_with_exception, "CSharpAssembly1", expectedMessage), result.ErrorMessage); + + var exception = exceptions.Single(); + AssertEx.Equal($"CSharpAssembly1: [190..190) 'var x = s.ToString();': NullReferenceException: '{expectedMessage}'", Inspect(exception, query)); + AssertEx.Equal( + $" at Program.<
$>g__F|0_1(ISymbol s) in {FeaturesResources.Query}:line 11" + Environment.NewLine + + $" at Program.<>c.<
$>b__0_2(ISymbol x) in {FeaturesResources.Query}:line 5" + Environment.NewLine + + $" at System.Linq.Enumerable.SelectArrayIterator`2.MoveNext()" + Environment.NewLine, + exception.StackTrace.JoinText()); + } +} diff --git a/src/Features/CSharpTest/SemanticSearch/Mocks/MockSemanticSearchResultsObserver.cs b/src/Features/CSharpTest/SemanticSearch/Mocks/MockSemanticSearchResultsObserver.cs new file mode 100644 index 0000000000000..be7cf4b27cc8a --- /dev/null +++ b/src/Features/CSharpTest/SemanticSearch/Mocks/MockSemanticSearchResultsObserver.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.SemanticSearch; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SemanticSearch; + +internal class MockSemanticSearchResultsObserver : ISemanticSearchResultsObserver +{ + public Action? OnDefinitionFoundImpl { get; set; } + public Action? OnUserCodeExceptionImpl { get; set; } + public Action>? OnCompilationFailureImpl { get; set; } + public Action? ItemsCompletedImpl { get; set; } + public Action? AddItemsImpl { get; set; } + + public ValueTask AddItemsAsync(int itemCount, CancellationToken cancellationToken) + { + AddItemsImpl?.Invoke(itemCount); + return ValueTaskFactory.CompletedTask; + } + + public ValueTask ItemsCompletedAsync(int itemCount, CancellationToken cancellationToken) + { + ItemsCompletedImpl?.Invoke(itemCount); + return ValueTaskFactory.CompletedTask; + } + + public ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) + { + OnDefinitionFoundImpl?.Invoke(definition); + return ValueTaskFactory.CompletedTask; + } + + public ValueTask OnUserCodeExceptionAsync(UserCodeExceptionInfo exception, CancellationToken cancellationToken) + { + OnUserCodeExceptionImpl?.Invoke(exception); + return ValueTaskFactory.CompletedTask; + } + + public ValueTask OnCompilationFailureAsync(ImmutableArray errors, CancellationToken cancellationToken) + { + OnCompilationFailureImpl?.Invoke(errors); + return ValueTaskFactory.CompletedTask; + } +} + diff --git a/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.State.cs b/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.State.cs index 0631da4cd583f..043bbc57b1488 100644 --- a/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.State.cs +++ b/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.State.cs @@ -92,7 +92,7 @@ private static async Task> GetConstructorCa } } - return applicableConstructors.ToImmutable(); + return applicableConstructors.ToImmutableAndClear(); } private static async Task IsApplicableConstructorAsync(IMethodSymbol constructor, Document document, ImmutableArray parameterNamesForSelectedMembers, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.cs b/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.cs index ef233f3e5f4af..583adc338a0e3 100644 --- a/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.cs @@ -108,7 +108,7 @@ private static ImmutableArray GetGroupedActions(AddConstructorParame actions.Add(result.OptionalParameterActions.Single()); } - return actions.ToImmutable(); + return actions.ToImmutableAndClear(); } private static AddConstructorParameterResult CreateCodeActions(Document document, CodeGenerationContextInfo info, State state) @@ -181,7 +181,7 @@ public async Task> ComputeIntentAsync( return []; } - using var _ = ArrayBuilder.GetInstance(out var results); + var results = new FixedSizeArrayBuilder(actions.Length); foreach (var action in actions) { // Intents currently have no way to report progress. @@ -192,6 +192,6 @@ public async Task> ComputeIntentAsync( results.Add(intent); } - return results.ToImmutable(); + return results.MoveToImmutable(); } } diff --git a/src/Features/Core/Portable/AddFileBanner/AbstractAddFileBannerCodeRefactoringProvider.cs b/src/Features/Core/Portable/AddFileBanner/AbstractAddFileBannerCodeRefactoringProvider.cs index c49e2d8fcdf05..1abbea0e73f17 100644 --- a/src/Features/Core/Portable/AddFileBanner/AbstractAddFileBannerCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/AddFileBanner/AbstractAddFileBannerCodeRefactoringProvider.cs @@ -148,18 +148,16 @@ private ImmutableArray UpdateEmbeddedFileNames( var sourceName = IOUtilities.PerformIO(() => Path.GetFileName(sourceDocument.FilePath)); var destinationName = IOUtilities.PerformIO(() => Path.GetFileName(destinationDocument.FilePath)); if (string.IsNullOrEmpty(sourceName) || string.IsNullOrEmpty(destinationName)) - { return banner; - } - using var _ = ArrayBuilder.GetInstance(out var result); + var result = new FixedSizeArrayBuilder(banner.Length); foreach (var trivia in banner) { var updated = CreateTrivia(trivia, trivia.ToFullString().Replace(sourceName, destinationName)); result.Add(updated); } - return result.ToImmutable(); + return result.MoveToImmutable(); } private async Task> TryGetBannerAsync( diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 99094f7dcdd97..84a7d0ffe9b66 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -121,7 +121,7 @@ private async Task> GetFixesInCurrentProcessAsy } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private async Task> FindResultsAsync( @@ -192,7 +192,7 @@ await FindResultsInAllSymbolsInStartingProjectAsync( } } - return allReferences.ToImmutableArray(); + return [.. allReferences]; } private static async Task FindResultsInAllSymbolsInStartingProjectAsync( @@ -484,7 +484,7 @@ private static bool NotNull(SymbolReference reference) // We might have multiple different diagnostics covering the same span. Have to // process them all as we might produce different fixes for each diagnostic. - using var _ = ArrayBuilder<(Diagnostic, ImmutableArray)>.GetInstance(out var result); + var result = new FixedSizeArrayBuilder<(Diagnostic, ImmutableArray)>(diagnostics.Length); foreach (var diagnostic in diagnostics) { @@ -496,7 +496,7 @@ private static bool NotNull(SymbolReference reference) result.Add((diagnostic, fixes)); } - return result.ToImmutable(); + return result.MoveToImmutable(); } public async Task> GetUniqueFixesAsync( @@ -562,7 +562,7 @@ private async Task> GetUniqueFixesAsyncInCurren } } - return fixes.ToImmutable(); + return fixes.ToImmutableAndClear(); } public ImmutableArray GetCodeActionsForFixes( @@ -578,7 +578,7 @@ public ImmutableArray GetCodeActionsForFixes( break; } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static CodeAction? TryCreateCodeAction(Document document, AddImportFixData fixData, IPackageInstallerService? installerService) diff --git a/src/Features/Core/Portable/AddImport/CodeActions/InstallPackageAndAddImportCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/InstallPackageAndAddImportCodeAction.cs index 1dd12679dd76c..6030a278ba6ab 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/InstallPackageAndAddImportCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/InstallPackageAndAddImportCodeAction.cs @@ -59,7 +59,7 @@ protected override async Task> ComputePreviewOp result.AddRange(await solutionChangeAction.GetPreviewOperationsAsync( this.OriginalDocument.Project.Solution, cancellationToken).ConfigureAwait(false)); result.Add(_installOperation); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private async Task GetUpdatedSolutionAsync(CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs index c9b9e26deef5d..49b2c38c311b3 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/SymbolReference.SymbolReferenceCodeAction.cs @@ -33,12 +33,7 @@ protected SymbolReferenceCodeAction( protected override async Task> ComputePreviewOperationsAsync(CancellationToken cancellationToken) { var operation = await GetChangeSolutionOperationAsync(isPreview: true, cancellationToken).ConfigureAwait(false); - if (operation is null) - { - return []; - } - - return SpecializedCollections.SingletonEnumerable(operation); + return operation is null ? [] : [operation]; } protected override async Task> ComputeOperationsAsync( diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs index 57508f95172f4..945aa8594c7ff 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs @@ -138,12 +138,11 @@ private async Task> DoAsync(SearchScope searchSc private ImmutableArray DeDupeAndSortReferences(ImmutableArray allReferences) { - return allReferences + return [.. allReferences .Distinct() .Where(NotNull) .Where(NotGlobalNamespace) - .OrderBy((r1, r2) => r1.CompareTo(_document, r2)) - .ToImmutableArray(); + .OrderBy((r1, r2) => r1.CompareTo(_document, r2))]; } private static void CalculateContext( @@ -602,7 +601,7 @@ private ImmutableArray GetNamespaceSymbolReferences( references.Add(scope.CreateReference(mappedResult)); } - return references.ToImmutable(); + return references.ToImmutableAndClear(); } private static ImmutableArray> OfType(ImmutableArray> symbols) where T : ISymbol diff --git a/src/Features/Core/Portable/AddPackage/ParentInstallPackageCodeAction.cs b/src/Features/Core/Portable/AddPackage/ParentInstallPackageCodeAction.cs index 08704b0019666..4a240ec50b781 100644 --- a/src/Features/Core/Portable/AddPackage/ParentInstallPackageCodeAction.cs +++ b/src/Features/Core/Portable/AddPackage/ParentInstallPackageCodeAction.cs @@ -101,7 +101,7 @@ private static ImmutableArray CreateNestedActions( // And finally the action to show the package manager dialog. codeActions.Add(new InstallWithPackageManagerCodeAction(installerService, fixData.PackageName)); - return codeActions.ToImmutable(); + return codeActions.ToImmutableAndClear(); } private static CodeAction CreateCodeAction( diff --git a/src/Features/Core/Portable/BracePairs/IBracePairsService.cs b/src/Features/Core/Portable/BracePairs/IBracePairsService.cs index 3fcf8b813061d..f03913f000012 100644 --- a/src/Features/Core/Portable/BracePairs/IBracePairsService.cs +++ b/src/Features/Core/Portable/BracePairs/IBracePairsService.cs @@ -50,9 +50,8 @@ public async Task AddBracePairsAsync( stack.Add(root); - while (stack.Count > 0) + while (stack.TryPop(out var current)) { - var current = stack.Pop(); if (current.IsNode) { // Ignore nodes that have no intersection at all with the span we're being asked with. Note: if diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index 87850319e07e0..99322262f53c0 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -737,16 +737,20 @@ protected static int GetParameterIndex(SeparatedSyntaxList paramet protected ImmutableArray GetSeparators(SeparatedSyntaxList arguments, int numSeparatorsToSkip) where T : SyntaxNode { - var separators = ImmutableArray.CreateBuilder(); + var count = arguments.SeparatorCount - numSeparatorsToSkip; + if (count < 0) + return []; - for (var i = 0; i < arguments.SeparatorCount - numSeparatorsToSkip; i++) + var separators = new FixedSizeArrayBuilder(count); + + for (var i = 0; i < count; i++) { separators.Add(i < arguments.SeparatorCount ? arguments.GetSeparator(i) : CommaTokenWithElasticSpace()); } - return separators.ToImmutable(); + return separators.MoveToImmutable(); } protected virtual async Task> AddNewArgumentsToListAsync( @@ -1009,7 +1013,7 @@ protected ImmutableArray GetPermutedDocCommentTrivia(SyntaxNode no updatedLeadingTrivia.Add(newTrivia); } - var extraNodeList = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var extraNodeList); while (index < permutedParamNodes.Length) { extraNodeList.Add(permutedParamNodes[index]); @@ -1027,9 +1031,7 @@ protected ImmutableArray GetPermutedDocCommentTrivia(SyntaxNode no updatedLeadingTrivia.Add(newTrivia); } - extraNodeList.Free(); - - return updatedLeadingTrivia.ToImmutable(); + return updatedLeadingTrivia.ToImmutableAndClear(); } protected static bool IsParamsArrayExpandedHelper(ISymbol symbol, int argumentCount, bool lastArgumentIsNamed, SemanticModel semanticModel, SyntaxNode lastArgumentExpression, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/ChangeSignature/ChangeSignatureCodeAction.cs b/src/Features/Core/Portable/ChangeSignature/ChangeSignatureCodeAction.cs index d6f7f10fac8c1..75f19be87a1cd 100644 --- a/src/Features/Core/Portable/ChangeSignature/ChangeSignatureCodeAction.cs +++ b/src/Features/Core/Portable/ChangeSignature/ChangeSignatureCodeAction.cs @@ -38,11 +38,9 @@ protected override async Task> ComputeOperation var changeSignatureResult = await _changeSignatureService.ChangeSignatureWithContextAsync(_context, changeSignatureOptions, cancellationToken).ConfigureAwait(false); if (changeSignatureResult.Succeeded) - { - return SpecializedCollections.SingletonEnumerable(new ChangeSignatureCodeActionOperation(changeSignatureResult.UpdatedSolution, changeSignatureResult.ConfirmationMessage)); - } + return [new ChangeSignatureCodeActionOperation(changeSignatureResult.UpdatedSolution, changeSignatureResult.ConfirmationMessage)]; } - return SpecializedCollections.EmptyEnumerable(); + return []; } } diff --git a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs index da3355a3344b3..fab481046098c 100644 --- a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs +++ b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -56,23 +57,30 @@ protected override async ValueTask> DetermineCascadedSym } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return Task.FromResult(project.Documents.ToImmutableArray()); + foreach (var document in project.Documents) + processResult(document, processResultData); + + return Task.CompletedTask; } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -83,7 +91,12 @@ protected override async ValueTask> FindReference var root = state.Root; var nodes = root.DescendantNodes(); - using var _ = ArrayBuilder.GetInstance(out var convertedAnonymousFunctions); + var invocations = nodes.Where(syntaxFacts.IsInvocationExpression) + .Where(e => state.SemanticModel.GetSymbolInfo(e, cancellationToken).Symbol?.OriginalDefinition == methodSymbol); + + foreach (var node in invocations) + processResult(CreateFinderLocation(node, state, cancellationToken), processResultData); + foreach (var node in nodes) { if (!syntaxFacts.IsAnonymousFunctionExpression(node)) @@ -97,14 +110,17 @@ protected override async ValueTask> FindReference } if (convertedType == methodSymbol.ContainingType) - convertedAnonymousFunctions.Add(node); + { + var finderLocation = CreateFinderLocation(node, state, cancellationToken); + processResult(finderLocation, processResultData); + } } - var invocations = nodes.Where(syntaxFacts.IsInvocationExpression) - .Where(e => state.SemanticModel.GetSymbolInfo(e, cancellationToken).Symbol?.OriginalDefinition == methodSymbol); + return; - return invocations.Concat(convertedAnonymousFunctions).SelectAsArray( - node => new FinderLocation( + static FinderLocation CreateFinderLocation(SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) + { + return new FinderLocation( node, new ReferenceLocation( state.Document, @@ -113,6 +129,7 @@ protected override async ValueTask> FindReference isImplicit: false, GetSymbolUsageInfo(node, state, cancellationToken), GetAdditionalFindUsagesProperties(node, state), - CandidateReason.None))); + CandidateReason.None)); + } } } diff --git a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs index 3422e33b99dc9..2d332d593df81 100644 --- a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs +++ b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs @@ -203,7 +203,7 @@ public static Task ConfigureCodeStyleOptionAsync( Project project, CancellationToken cancellationToken) => ConfigureCodeStyleOptionsAsync( - SpecializedCollections.SingletonEnumerable((optionName, optionValue, isPerLanguage)), + [(optionName, optionValue, isPerLanguage)], diagnostic.Severity.ToEditorConfigString(), diagnostic, project, configurationKind: ConfigurationKind.OptionValue, cancellationToken); @@ -350,22 +350,15 @@ private async Task ConfigureAsync(CancellationToken cancellationToken) var codeStyleOptions = GetCodeStyleOptionsForDiagnostic(diagnostic, project); if (!codeStyleOptions.IsEmpty) { - var builder = ArrayBuilder<(string optionName, string currentOptionValue, bool isPerLanguage)>.GetInstance(); + var builder = new FixedSizeArrayBuilder<(string optionName, string currentOptionValue, bool isPerLanguage)>(codeStyleOptions.Length); - try + foreach (var option in codeStyleOptions) { - foreach (var option in codeStyleOptions) - { - var optionValue = option.Definition.Serializer.Serialize(option.DefaultValue); - builder.Add((option.Definition.ConfigName, optionValue, option.IsPerLanguage)); - } - - return builder.ToImmutable(); - } - finally - { - builder.Free(); + var optionValue = option.Definition.Serializer.Serialize(option.DefaultValue); + builder.Add((option.Definition.ConfigName, optionValue, option.IsPerLanguage)); } + + return builder.MoveToImmutable(); } return []; @@ -392,10 +385,10 @@ internal static ImmutableArray GetCodeStyleOptionsForDiagnostic(Diagno { if (IDEDiagnosticIdToOptionMappingHelper.TryGetMappedOptions(diagnostic.Id, project.Language, out var options)) { - return (from option in options - where option.DefaultValue is ICodeStyleOption - orderby option.Definition.ConfigName - select option).ToImmutableArray(); + return [.. from option in options + where option.DefaultValue is ICodeStyleOption + orderby option.Definition.ConfigName + select option]; } return []; diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs index 9bb5419aa19c0..5b0a56aaff0be 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs @@ -110,7 +110,7 @@ internal abstract class AbstractSuppressionBatchFixAllProvider : FixAllProvider await Task.WhenAll(tasks).ConfigureAwait(false); } - return fixesBag.ToImmutableArray(); + return [.. fixesBag]; } private async Task AddDocumentFixesAsync( @@ -202,11 +202,10 @@ private static Action> GetRegisterCodeFix { return (action, diagnostics) => { - using var _ = ArrayBuilder.GetInstance(out var builder); - builder.Push(action); - while (builder.Count > 0) + using var _ = ArrayBuilder.GetInstance(out var stack); + stack.Push(action); + while (stack.TryPop(out var currentAction)) { - var currentAction = builder.Pop(); if (currentAction is { EquivalenceKey: var equivalenceKey } && equivalenceKey == fixAllState.CodeActionEquivalenceKey) { @@ -215,7 +214,7 @@ private static Action> GetRegisterCodeFix foreach (var nestedAction in currentAction.NestedActions) { - builder.Push(nestedAction); + stack.Push(nestedAction); } } }; diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageFixAllCodeAction.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageFixAllCodeAction.cs index 5eb68373b0d5c..0db9273f39dbf 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageFixAllCodeAction.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageFixAllCodeAction.cs @@ -211,9 +211,7 @@ private static void AddDiagnosticForSymbolIfNeeded(ISymbol targetSymbol, Diagnos private static IEnumerable>> CreateDiagnosticsBySymbol(ImmutableDictionary>.Builder diagnosticsMapBuilder) { if (diagnosticsMapBuilder.Count == 0) - { - return SpecializedCollections.EmptyEnumerable>>(); - } + return []; var builder = new List>>(); foreach (var (symbol, diagnostics) in diagnosticsMapBuilder) diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaBatchFixHelpers.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaBatchFixHelpers.cs index 25a8fcd24a3f3..81bd3002bced4 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaBatchFixHelpers.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaBatchFixHelpers.cs @@ -86,7 +86,7 @@ private static async Task BatchPragmaFixesAsync( properties: diagnostic.Properties, isSuppressed: diagnostic.IsSuppressed); - var newSuppressionFixes = await suppressionFixProvider.GetFixesAsync(currentDocument, currentDiagnosticSpan, SpecializedCollections.SingletonEnumerable(diagnostic), fallbackOptions, cancellationToken).ConfigureAwait(false); + var newSuppressionFixes = await suppressionFixProvider.GetFixesAsync(currentDocument, currentDiagnosticSpan, [diagnostic], fallbackOptions, cancellationToken).ConfigureAwait(false); var newSuppressionFix = newSuppressionFixes.SingleOrDefault(); if (newSuppressionFix != null) { diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs index c748295d4eabb..e0c7a073532e0 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs @@ -164,11 +164,11 @@ internal static SyntaxToken GetNewEndTokenWithAddedPragma( var isEOF = fixer.IsEndOfFileToken(endToken); if (isEOF) { - trivia = endToken.LeadingTrivia.ToImmutableArray(); + trivia = [.. endToken.LeadingTrivia]; } else { - trivia = endToken.TrailingTrivia.ToImmutableArray(); + trivia = [.. endToken.TrailingTrivia]; } var index = GetPositionForPragmaInsertion(trivia, currentDiagnosticSpan, fixer, isStartToken: false, triviaAtIndex: out var insertBeforeTrivia); diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs index 33bf3f7e1b06e..bce19c8db6a56 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs @@ -36,7 +36,7 @@ protected override async Task AddDocumentFixesAsync( { var span = diagnostic.Location.SourceSpan; var pragmaSuppressions = await _suppressionFixProvider.GetPragmaSuppressionsAsync( - document, span, SpecializedCollections.SingletonEnumerable(diagnostic), fixAllState.CodeActionOptionsProvider, cancellationToken).ConfigureAwait(false); + document, span, [diagnostic], fixAllState.CodeActionOptionsProvider, cancellationToken).ConfigureAwait(false); var pragmaSuppression = pragmaSuppressions.SingleOrDefault(); if (pragmaSuppression != null) { diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs index ec90e430ddf57..a52525a81854c 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs @@ -44,7 +44,7 @@ protected override async Task AddDocumentFixesAsync( { var span = diagnostic.Location.SourceSpan; var removeSuppressionFixes = await _suppressionFixProvider.GetFixesAsync( - document, span, SpecializedCollections.SingletonEnumerable(diagnostic), fixAllState.CodeActionOptionsProvider, cancellationToken).ConfigureAwait(false); + document, span, [diagnostic], fixAllState.CodeActionOptionsProvider, cancellationToken).ConfigureAwait(false); var removeSuppressionFix = removeSuppressionFixes.SingleOrDefault(); if (removeSuppressionFix != null) { @@ -89,7 +89,7 @@ protected override async Task AddProjectFixesAsync( foreach (var diagnostic in diagnostics.Where(d => !d.Location.IsInSource && d.IsSuppressed)) { var removeSuppressionFixes = await _suppressionFixProvider.GetFixesAsync( - project, SpecializedCollections.SingletonEnumerable(diagnostic), fixAllState.CodeActionOptionsProvider, cancellationToken).ConfigureAwait(false); + project, [diagnostic], fixAllState.CodeActionOptionsProvider, cancellationToken).ConfigureAwait(false); if (removeSuppressionFixes.SingleOrDefault()?.Action is RemoveSuppressionCodeAction removeSuppressionCodeAction) { if (fixAllState.IsFixMultiple) @@ -153,19 +153,19 @@ public override async Task TryGetMergedFixAsync( } return await base.TryGetMergedFixAsync( - newBatchOfFixes.ToImmutableArray(), fixAllState, progressTracker, cancellationToken).ConfigureAwait(false); + [.. newBatchOfFixes], fixAllState, progressTracker, cancellationToken).ConfigureAwait(false); } private static async Task> GetAttributeNodesToFixAsync(ImmutableArray attributeRemoveFixes, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(attributeRemoveFixes.Length, out var builder); + var builder = new FixedSizeArrayBuilder(attributeRemoveFixes.Length); foreach (var attributeRemoveFix in attributeRemoveFixes) { var attributeToRemove = await attributeRemoveFix.GetAttributeToRemoveAsync(cancellationToken).ConfigureAwait(false); builder.Add(attributeToRemove); } - return builder.ToImmutableAndClear(); + return builder.MoveToImmutable(); } } } diff --git a/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs b/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs index 076e4ddaabbdb..5a0a535ac915c 100644 --- a/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs +++ b/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs @@ -48,7 +48,7 @@ internal sealed class CodeLensFindReferencesProgress( public int ReferencesCount => _locations.Count; - public ImmutableArray Locations => _locations.ToImmutableArray(); + public ImmutableArray Locations => [.. _locations]; public void OnStarted() { diff --git a/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs b/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs index 53ed67ddf0918..11ac6cc583cc9 100644 --- a/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/AbstractRefactoringHelpersService.cs @@ -32,7 +32,7 @@ public async Task> GetRelevantNodesAsync.GetInstance(out var nonEmptyNodes); foreach (var node in relevantNodesBuilder) diff --git a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs index 77cffe24feeb2..49dd3e0320e36 100644 --- a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs @@ -34,7 +34,7 @@ internal sealed class CodeRefactoringService( ImmutableDictionary.CreateRange( DistributeLanguages(providers) .GroupBy(lz => lz.Metadata.Language) - .Select(grp => new KeyValuePair>>( + .Select(grp => KeyValuePairUtil.Create( grp.Key, new Lazy>(() => ExtensionOrderer.Order(grp).Select(lz => lz.Value).ToImmutableArray()))))); private readonly Lazy> _lazyRefactoringToMetadataMap = new(() => providers.Where(provider => provider.IsValueCreated).ToImmutableDictionary(provider => provider.Value, provider => provider.Metadata)); @@ -212,7 +212,7 @@ private class ProjectCodeRefactoringProvider : AbstractProjectExtensionProvider { protected override ImmutableArray GetLanguages(ExportCodeRefactoringProviderAttribute exportAttribute) - => exportAttribute.Languages.ToImmutableArray(); + => [.. exportAttribute.Languages]; protected override bool TryGetExtensionsFromReference(AnalyzerReference reference, out ImmutableArray extensions) { diff --git a/src/Features/Core/Portable/CodeRefactorings/ExtractMethod/AbstractExtractMethodCodeRefactoringProvider.cs b/src/Features/Core/Portable/CodeRefactorings/ExtractMethod/AbstractExtractMethodCodeRefactoringProvider.cs index 0a9847d7caba7..e58d370f34b55 100644 --- a/src/Features/Core/Portable/CodeRefactorings/ExtractMethod/AbstractExtractMethodCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/CodeRefactorings/ExtractMethod/AbstractExtractMethodCodeRefactoringProvider.cs @@ -66,7 +66,7 @@ private static async Task> GetCodeActionsAsync( var localFunctionAction = await ExtractLocalFunctionAsync(document, textSpan, extractOptions, cancellationToken).ConfigureAwait(false); actions.AddIfNotNull(localFunctionAction); - return actions.ToImmutable(); + return actions.ToImmutableAndClear(); } private static async Task ExtractMethodAsync( diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs index 1cf7f7b7d46e5..fd88a578f05cf 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs @@ -125,7 +125,7 @@ private async Task AddNewDocumentWithSingleTypeDeclarationAsync(Docume // attributes from the containing partial types. We don't want to create // duplicate attributes on things. AddPartialModifiersToTypeChain( - documentEditor, removeAttributesAndComments: true, removeTypeInheritance: true); + documentEditor, removeAttributesAndComments: true, removeTypeInheritance: true, removePrimaryConstructor: true); // remove things that are not being moved, from the forked document. var membersToRemove = GetMembersToRemove(root); @@ -193,7 +193,7 @@ private async Task RemoveTypeFromSourceDocumentAsync(Document sourceDo // However, keep all the attributes on these types as theses are the // original attributes and we don't want to mess with them. AddPartialModifiersToTypeChain(documentEditor, - removeAttributesAndComments: false, removeTypeInheritance: false); + removeAttributesAndComments: false, removeTypeInheritance: false, removePrimaryConstructor: false); documentEditor.RemoveNode(State.TypeNode, SyntaxRemoveOptions.KeepUnbalancedDirectives); var updatedDocument = documentEditor.GetChangedDocument(); @@ -258,7 +258,8 @@ TMemberDeclarationSyntax or private void AddPartialModifiersToTypeChain( DocumentEditor documentEditor, bool removeAttributesAndComments, - bool removeTypeInheritance) + bool removeTypeInheritance, + bool removePrimaryConstructor) { var semanticFacts = State.SemanticDocument.Document.GetRequiredLanguageService(); var typeChain = State.TypeNode.Ancestors().OfType(); @@ -283,6 +284,11 @@ private void AddPartialModifiersToTypeChain( { documentEditor.RemoveAllTypeInheritance(node); } + + if (removePrimaryConstructor) + { + documentEditor.RemovePrimaryConstructor(node); + } } documentEditor.ReplaceNode(State.TypeNode, diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs index a60133133f9ea..bb3804312b745 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs @@ -160,7 +160,7 @@ private ImmutableArray CreateActions(State state, CancellationToken Debug.Assert(actions.Count != 0, "No code actions found for MoveType Refactoring"); - return actions.ToImmutable(); + return actions.ToImmutableAndClear(); } private static bool ClassNextToGlobalStatements(SyntaxNode root, ISyntaxFactsService syntaxFacts) diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index 6a945ab96565f..b9dd43e57681d 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -259,7 +259,7 @@ await ChangeNamespaceInSingleDocumentAsync(solutionAfterNamespaceChange, documen solutionAfterImportsRemoved = await RemoveUnnecessaryImportsAsync( solutionAfterImportsRemoved, - referenceDocuments.ToImmutableArray(), + [.. referenceDocuments], [declaredNamespace, targetNamespace], fallbackOptions, cancellationToken).ConfigureAwait(false); @@ -403,11 +403,11 @@ private static ImmutableArray GetAllNamespaceImportsForDeclaringDocument private static ImmutableArray CreateImports(Document document, ImmutableArray names, bool withFormatterAnnotation) { var generator = SyntaxGenerator.GetGenerator(document); - using var _ = ArrayBuilder.GetInstance(names.Length, out var builder); + var builder = new FixedSizeArrayBuilder(names.Length); for (var i = 0; i < names.Length; ++i) builder.Add(CreateImport(generator, names[i], withFormatterAnnotation)); - return builder.ToImmutableAndClear(); + return builder.MoveToImmutable(); } private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string name, bool withFormatterAnnotation) @@ -553,7 +553,7 @@ private static async Task> FindReferen } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private static async Task> FindReferencesAsync(ISymbol symbol, Document document, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs index 9f9afa686ebeb..5a7dbf9fb95b9 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs @@ -158,7 +158,7 @@ private static bool IsDocumentPathRootedInProjectFolder(Document document) if (projectRoot is null) return false; - var folderPath = Path.Combine(document.Folders.ToArray()); + var folderPath = Path.Combine([.. document.Folders]); var logicalDirectoryPath = PathUtilities.CombineAbsoluteAndRelativePaths(projectRoot, folderPath); if (logicalDirectoryPath is null) return false; diff --git a/src/Features/Core/Portable/Common/AbstractProjectExtensionProvider.cs b/src/Features/Core/Portable/Common/AbstractProjectExtensionProvider.cs index 69d3d9d57adf2..480fa03983ebf 100644 --- a/src/Features/Core/Portable/Common/AbstractProjectExtensionProvider.cs +++ b/src/Features/Core/Portable/Common/AbstractProjectExtensionProvider.cs @@ -73,7 +73,7 @@ static ImmutableArray ComputeExtensions(string language, IReadOnlyLi builder.Add(extension); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } @@ -178,6 +178,6 @@ private ImmutableArray CreateExtensions(string language) // NOTE: We could report "unable to load analyzer" exception here but it should have been already reported by DiagnosticService. } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/Common/UpdatedEventArgs.cs b/src/Features/Core/Portable/Common/UpdatedEventArgs.cs deleted file mode 100644 index a3bd810e1cffa..0000000000000 --- a/src/Features/Core/Portable/Common/UpdatedEventArgs.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; - -namespace Microsoft.CodeAnalysis.Common; - -internal class UpdatedEventArgs(object id, Workspace workspace, ProjectId? projectId, DocumentId? documentId) : EventArgs -{ - /// - /// The identity of update group. - /// - public object Id { get; } = id; - - /// - /// this update is associated with. - /// - public Workspace Workspace { get; } = workspace; - - /// - /// this update is associated with, or . - /// - public ProjectId? ProjectId { get; } = projectId; - - /// - /// this update is associated with, or . - /// - public DocumentId? DocumentId { get; } = documentId; -} diff --git a/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs b/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs index 9115fa8976a73..b05169033da52 100644 --- a/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs +++ b/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs @@ -43,5 +43,5 @@ public static CharacterSetModificationRule Create(CharacterSetModificationKind k /// One or more characters. These are typically punctuation characters. /// public static CharacterSetModificationRule Create(CharacterSetModificationKind kind, params char[] characters) - => new(kind, characters.ToImmutableArray()); + => new(kind, [.. characters]); } diff --git a/src/Features/Core/Portable/Completion/CommonCompletionItem.cs b/src/Features/Core/Portable/Completion/CommonCompletionItem.cs index 5e7b8827ea033..271e8fafee92b 100644 --- a/src/Features/Core/Portable/Completion/CommonCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/CommonCompletionItem.cs @@ -44,7 +44,7 @@ public static CompletionItem Create( if (!description.IsDefault && description.Length > 0) { - properties = properties.NullToEmpty().Add(new KeyValuePair(DescriptionProperty, EncodeDescription(description.ToTaggedText()))); + properties = properties.NullToEmpty().Add(KeyValuePairUtil.Create(DescriptionProperty, EncodeDescription(description.ToTaggedText()))); } return CompletionItem.CreateInternal( diff --git a/src/Features/Core/Portable/Completion/CompletionItem.cs b/src/Features/Core/Portable/Completion/CompletionItem.cs index 1ff6fe6482976..189d6c0dc7a7e 100644 --- a/src/Features/Core/Portable/Completion/CompletionItem.cs +++ b/src/Features/Core/Portable/Completion/CompletionItem.cs @@ -461,7 +461,7 @@ internal CompletionItem WithProperties(ImmutableArray with the specified property. ///
public CompletionItem AddProperty(string name, string value) - => With(properties: GetProperties().Add(new KeyValuePair(name, value))); + => With(properties: GetProperties().Add(KeyValuePairUtil.Create(name, value))); /// /// Creates a copy of this with the property changed. diff --git a/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs b/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs index 9c707d7f8cb70..5095803afa12f 100644 --- a/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs +++ b/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs @@ -137,13 +137,13 @@ ImmutableArray GetTriggeredProviders( var triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(document.Project.Services, text, caretPosition, trigger, options, passThroughOptions)).ToImmutableArrayOrEmpty(); Debug.Assert(ValidatePossibleTriggerCharacterSet(trigger.Kind, triggeredProviders, document, text, caretPosition, options)); - return triggeredProviders.IsEmpty ? providers.ToImmutableArray() : triggeredProviders; + return triggeredProviders.IsEmpty ? [.. providers] : triggeredProviders; } return []; default: - return providers.ToImmutableArray(); + return [.. providers]; } } diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs index 1ae291e7c7054..75a94ffb6e154 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs @@ -101,15 +101,15 @@ public sealed override async Task ProvideCompletionsAsync(CompletionContext cont using var builder = TemporaryArray>.Empty; - builder.Add(new KeyValuePair(AwaitCompletionTargetTokenPosition, token.SpanStart.ToString())); + builder.Add(KeyValuePairUtil.Create(AwaitCompletionTargetTokenPosition, token.SpanStart.ToString())); var makeContainerAsync = declaration is not null && !SyntaxGenerator.GetGenerator(document).GetModifiers(declaration).IsAsync; if (makeContainerAsync) - builder.Add(new KeyValuePair(MakeContainerAsync, string.Empty)); + builder.Add(KeyValuePairUtil.Create(MakeContainerAsync, string.Empty)); if (isAwaitKeywordContext) { - builder.Add(new KeyValuePair(AddAwaitAtCurrentPosition, string.Empty)); + builder.Add(KeyValuePairUtil.Create(AddAwaitAtCurrentPosition, string.Empty)); var properties = builder.ToImmutableAndClear(); context.AddItem(CreateCompletionItem( @@ -134,7 +134,7 @@ public sealed override async Task ProvideCompletionsAsync(CompletionContext cont if (dotAwaitContext == DotAwaitContext.AwaitAndConfigureAwait) { // add the `awaitf` option to do the same, but also add .ConfigureAwait(false); - properties = properties.Add(new KeyValuePair(AppendConfigureAwait, string.Empty)); + properties = properties.Add(KeyValuePairUtil.Create(AppendConfigureAwait, string.Empty)); context.AddItem(CreateCompletionItem( properties, _awaitfDisplayText, _awaitfFilterText, string.Format(FeaturesResources.Await_the_preceding_expression_and_add_ConfigureAwait_0, _falseKeyword), diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs index 0351f0944b2cb..4bf683611ab5c 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs @@ -94,7 +94,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) protected abstract IEnumerable GetExistingTopLevelAttributeValues(TSyntax syntax, string tagName, string attributeName); - protected abstract IEnumerable GetKeywordNames(); + protected abstract ImmutableArray GetKeywordNames(); /// /// A temporarily hack that should be removed once/if https://github.com/dotnet/roslyn/issues/53092 is fixed. @@ -207,7 +207,7 @@ protected IEnumerable GetAttributeValueItems(ISymbol? symbol, st return s_listTypeValues.Select(CreateCompletionItem); } - return SpecializedCollections.EmptyEnumerable(); + return []; } protected ImmutableArray GetTopLevelItems(ISymbol? symbol, TSyntax syntax) @@ -246,7 +246,7 @@ protected ImmutableArray GetTopLevelItems(ISymbol? symbol, TSynt } } - return items.ToImmutable(); + return items.ToImmutableAndClear(); } protected IEnumerable GetItemTagItems() diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractInternalsVisibleToCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractInternalsVisibleToCompletionProvider.cs index 2441c844fa572..2828d3e59299d 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractInternalsVisibleToCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractInternalsVisibleToCompletionProvider.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion.Providers; @@ -157,7 +158,7 @@ private async Task AddAssemblyCompletionItemsAsync(CompletionContext context, Ca displayTextSuffix: "", rules: CompletionItemRules.Default, glyph: project.GetGlyph(), - properties: [new KeyValuePair(ProjectGuidKey, projectGuid)]); + properties: [KeyValuePairUtil.Create(ProjectGuidKey, projectGuid)]); context.AddItem(completionItem); } diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs index 21398936c8f3e..730aff1b8f646 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs @@ -67,7 +67,7 @@ private async Task> RecommendKeywordsAsync( } result.RemoveDuplicates(); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public sealed override Task GetTextChangeAsync(Document document, CompletionItem item, char? ch, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractPartialTypeCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractPartialTypeCompletionProvider.cs index 65b0957c36f00..37f354e2f5c9b 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractPartialTypeCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractPartialTypeCompletionProvider.cs @@ -88,9 +88,7 @@ private CompletionItem CreateCompletionItem(INamedTypeSymbol symbol, TSyntaxCont var semanticModel = context.SemanticModel; if (declaredSymbol.ContainingSymbol is not INamespaceOrTypeSymbol containingSymbol) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return semanticModel.LookupNamespacesAndTypes(context.Position, containingSymbol) .OfType() diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs index 6a3062fe92f1b..6f7d9e28004e5 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs @@ -206,12 +206,30 @@ internal sealed override async Task GetDescriptionWorkerA var name = SymbolCompletionItem.GetSymbolName(item); var kind = SymbolCompletionItem.GetKind(item); var isGeneric = SymbolCompletionItem.GetSymbolIsGeneric(item); - var relatedDocumentIds = document.Project.Solution.GetRelatedDocumentIds(document.Id); var typeConvertibilityCache = new Dictionary(SymbolEqualityComparer.Default); + // First try with the document we're currently within. + var description = await TryGetDescriptionAsync(document.Id).ConfigureAwait(false); + if (description != null) + return description; + + // If that didn't work, see about any related documents. + var relatedDocumentIds = document.Project.Solution.GetRelatedDocumentIds(document.Id); foreach (var relatedId in relatedDocumentIds) { - var relatedDocument = document.Project.Solution.GetRequiredDocument(relatedId); + if (relatedId == document.Id) + continue; + + description = await TryGetDescriptionAsync(relatedId).ConfigureAwait(false); + if (description != null) + return description; + } + + return CompletionDescription.Empty; + + async Task TryGetDescriptionAsync(DocumentId documentId) + { + var relatedDocument = document.Project.Solution.GetRequiredDocument(documentId); var context = await Utilities.CreateSyntaxContextWithExistingSpeculativeModelAsync(relatedDocument, position, cancellationToken).ConfigureAwait(false) as TSyntaxContext; Contract.ThrowIfNull(context); var symbols = await TryGetSymbolsForContextAsync(completionContext: null, context, options, cancellationToken).ConfigureAwait(false); @@ -235,9 +253,9 @@ internal sealed override async Task GetDescriptionWorkerA return await SymbolCompletionItem.GetDescriptionAsync(item, bestSymbols.SelectAsArray(t => t.Symbol), document, context.SemanticModel, displayOptions, cancellationToken).ConfigureAwait(false); } } - } - return CompletionDescription.Empty; + return null; + } static bool SymbolMatches(SymbolAndSelectionInfo info, string? name, SymbolKind? kind, bool isGeneric) { diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs index f3a276a0b9718..8f7035e04710f 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs @@ -147,7 +147,7 @@ group symbol by texts into g itemListBuilder.Add(item); } - return itemListBuilder.ToImmutable(); + return itemListBuilder.ToImmutableAndClear(); } protected static bool TryFindFirstSymbolMatchesTargetTypes( @@ -277,7 +277,7 @@ private async Task> GetItemsAsync( var totalProjects = contextAndSymbolLists.Select(t => t.documentId.ProjectId).ToList(); return CreateItems( - completionContext, symbolToContextMap.Keys.ToImmutableArray(), symbol => symbolToContextMap[symbol], missingSymbolsMap, totalProjects); + completionContext, [.. symbolToContextMap.Keys], symbol => symbolToContextMap[symbol], missingSymbolsMap, totalProjects); } protected virtual bool IsExclusive() @@ -337,7 +337,7 @@ private static Dictionary UnionSymbols( perContextSymbols.Add((relatedDocumentId, syntaxContext, symbols)); } - return perContextSymbols.ToImmutable(); + return perContextSymbols.ToImmutableAndClear(); } /// diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs index 9aac29d32a390..9e34d43f12d36 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs @@ -114,7 +114,7 @@ private static ImmutableArray GetImportedNamespaces(SyntaxContext contex } } - return usingsBuilder.ToImmutable(); + return usingsBuilder.ToImmutableAndClear(); } public override async Task GetChangeAsync( diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs index 28b81bce83aa4..979bed9aade96 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs @@ -298,7 +298,7 @@ private ImmutableArray GetExtensionMethodsForSymbolsFromDifferent } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private ImmutableArray GetExtensionMethodsForSymbolsFromSameCompilation( @@ -342,7 +342,7 @@ private ImmutableArray GetExtensionMethodsForSymbolsFromSameCompi } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private MultiDictionary GetPotentialMatchingSymbolsFromAssembly( @@ -482,7 +482,7 @@ private static ImmutableArray GetReceiverTypeNames(ITypeSymbol receiverT { using var _ = PooledHashSet.GetInstance(out var allTypeNamesBuilder); AddNamesForTypeWorker(receiverTypeSymbol, allTypeNamesBuilder); - return allTypeNamesBuilder.ToImmutableArray(); + return [.. allTypeNamesBuilder]; static void AddNamesForTypeWorker(ITypeSymbol receiverTypeSymbol, PooledHashSet builder) { diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs index 58af5842ea690..13273cb105550 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs @@ -72,7 +72,7 @@ public static void WarmUpCacheInCurrentProcess(Project project) var remoteResult = await client.TryInvokeAsync( project, (service, solutionInfo, cancellationToken) => service.GetUnimportedExtensionMethodsAsync( - solutionInfo, document.Id, position, receiverTypeSymbolKeyData, namespaceInScope.ToImmutableArray(), + solutionInfo, document.Id, position, receiverTypeSymbolKeyData, [.. namespaceInScope], targetTypesSymbolKeyData, forceCacheCreation, hideAdvancedMembers, cancellationToken), cancellationToken).ConfigureAwait(false); @@ -188,7 +188,7 @@ private static ImmutableArray ConvertSymbolsTo } // Then convert symbols into completion items - using var _3 = ArrayBuilder.GetInstance(out var itemsBuilder); + var itemsBuilder = new FixedSizeArrayBuilder(overloadMap.Count); foreach (var ((containingNamespace, _, _), (bestSymbol, overloadCount, includeInTargetTypedCompletion)) in overloadMap) { @@ -205,7 +205,7 @@ private static ImmutableArray ConvertSymbolsTo itemsBuilder.Add(item); } - return itemsBuilder.ToImmutable(); + return itemsBuilder.MoveToImmutable(); } private static bool ShouldIncludeInTargetTypedCompletion( diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs index a1c2a4b673d50..5d714b7a07c13 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Tags; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion.Providers; @@ -46,19 +47,19 @@ public static CompletionItem Create( if (extensionMethodData.HasValue) { - builder.Add(new KeyValuePair(MethodKey, extensionMethodData.Value.methodSymbolKey)); - builder.Add(new KeyValuePair(ReceiverKey, extensionMethodData.Value.receiverTypeSymbolKey)); + builder.Add(KeyValuePairUtil.Create(MethodKey, extensionMethodData.Value.methodSymbolKey)); + builder.Add(KeyValuePairUtil.Create(ReceiverKey, extensionMethodData.Value.receiverTypeSymbolKey)); if (extensionMethodData.Value.overloadCount > 0) { - builder.Add(new KeyValuePair(OverloadCountKey, extensionMethodData.Value.overloadCount.ToString())); + builder.Add(KeyValuePairUtil.Create(OverloadCountKey, extensionMethodData.Value.overloadCount.ToString())); } } else { // We don't need arity to recover symbol if we already have SymbolKeyData or it's 0. // (but it still needed below to decide whether to show generic suffix) - builder.Add(new KeyValuePair(TypeAritySuffixName, ArityUtilities.GetMetadataAritySuffix(arity))); + builder.Add(KeyValuePairUtil.Create(TypeAritySuffixName, ArityUtilities.GetMetadataAritySuffix(arity))); } properties = builder.ToImmutable(); @@ -97,9 +98,9 @@ public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem att var attributeItems = attributeItem.GetProperties(); // Remember the full type name so we can get the symbol when description is displayed. - using var _ = ArrayBuilder>.GetInstance(attributeItems.Length + 1, out var builder); + var builder = new FixedSizeArrayBuilder>(attributeItems.Length + 1); builder.AddRange(attributeItems); - builder.Add(new KeyValuePair(AttributeFullName, attributeItem.DisplayText)); + builder.Add(KeyValuePairUtil.Create(AttributeFullName, attributeItem.DisplayText)); var sortTextBuilder = PooledStringBuilder.GetInstance(); sortTextBuilder.Builder.AppendFormat(GetSortTextFormatString(attributeItem.InlineDescription), attributeNameWithoutSuffix, attributeItem.InlineDescription); @@ -107,7 +108,7 @@ public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem att var item = CompletionItem.CreateInternal( displayText: attributeNameWithoutSuffix, sortText: sortTextBuilder.ToStringAndFree(), - properties: builder.ToImmutable(), + properties: builder.MoveToImmutable(), tags: attributeItem.Tags, rules: attributeItem.Rules, displayTextPrefix: attributeItem.DisplayTextPrefix, @@ -219,12 +220,8 @@ private static (ISymbol? symbol, int overloadCount) GetSymbolAndOverloadCount(Co public static CompletionItem MarkItemToAlwaysFullyQualify(CompletionItem item) { var itemProperties = item.GetProperties(); - - using var _ = ArrayBuilder>.GetInstance(itemProperties.Length + 1, out var builder); - builder.AddRange(itemProperties); - builder.Add(new KeyValuePair(AlwaysFullyQualifyKey, AlwaysFullyQualifyKey)); - - return item.WithProperties(builder.ToImmutable()); + ImmutableArray> properties = [.. itemProperties, KeyValuePairUtil.Create(AlwaysFullyQualifyKey, AlwaysFullyQualifyKey)]; + return item.WithProperties(properties); } public static bool ShouldAlwaysFullyQualify(CompletionItem item) => item.TryGetProperty(AlwaysFullyQualifyKey, out var _); diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/TypeImportCompletionCacheEntry.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/TypeImportCompletionCacheEntry.cs index c414b0124aaa9..c9ee742ce2d22 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/TypeImportCompletionCacheEntry.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/TypeImportCompletionCacheEntry.cs @@ -129,7 +129,7 @@ public ImmutableArray GetItemsForContext( builder.Add(item); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); static CompletionItem GetAppropriateAttributeItem(CompletionItem attributeItem, bool isCaseSensitive) { diff --git a/src/Features/Core/Portable/Completion/Providers/MemberInsertingCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/MemberInsertingCompletionItem.cs index 026b44ed4591e..ad5d486533cd3 100644 --- a/src/Features/Core/Portable/Completion/Providers/MemberInsertingCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/MemberInsertingCompletionItem.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.LanguageService; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion.Providers; @@ -23,17 +24,15 @@ public static CompletionItem Create( int descriptionPosition, CompletionItemRules rules) { - var props = ImmutableArray.Create( - new KeyValuePair("Line", line.ToString()), - new KeyValuePair("Modifiers", modifiers.ToString()), - new KeyValuePair("TokenSpanEnd", token.Span.End.ToString())); - return SymbolCompletionItem.CreateWithSymbolId( displayText: displayText, displayTextSuffix: displayTextSuffix, - symbols: ImmutableArray.Create(symbol), + symbols: [symbol], contextPosition: descriptionPosition, - properties: props, + properties: [ + KeyValuePairUtil.Create("Line", line.ToString()), + KeyValuePairUtil.Create("Modifiers", modifiers.ToString()), + KeyValuePairUtil.Create("TokenSpanEnd", token.Span.End.ToString())], rules: rules, isComplexTextEdit: true); } diff --git a/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs b/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs index 869ffdd646947..a4739b0a46790 100644 --- a/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs +++ b/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs @@ -40,6 +40,6 @@ internal static ImmutableArray CreateDisplayParts(string keyw textContentBuilder.AddText(toolTip); } - return textContentBuilder.ToImmutableArray(); + return [.. textContentBuilder]; } } diff --git a/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractLoadDirectiveCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractLoadDirectiveCompletionProvider.cs index 21b7f3f91b7d1..aa29c2cce9dcd 100644 --- a/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractLoadDirectiveCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractLoadDirectiveCompletionProvider.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion.Providers; @@ -22,7 +23,7 @@ internal abstract class AbstractLoadDirectiveCompletionProvider : AbstractDirect private static ImmutableArray GetCommitCharacters() { - using var builderDisposer = ArrayBuilder.GetInstance(out var builder); + using var builder = TemporaryArray.Empty; builder.Add('"'); if (PathUtilities.IsUnixLikePlatform) { @@ -34,7 +35,7 @@ private static ImmutableArray GetCommitCharacters() builder.Add('\\'); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } protected override async Task ProvideCompletionsAsync(CompletionContext context, string pathThroughLastSlash) diff --git a/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractReferenceDirectiveCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractReferenceDirectiveCompletionProvider.cs index 2776564e7fc6a..5c8db335d6373 100644 --- a/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractReferenceDirectiveCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractReferenceDirectiveCompletionProvider.cs @@ -44,7 +44,7 @@ private static ImmutableArray GetCommitCharacters() builder.Add(','); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } protected override async Task ProvideCompletionsAsync(CompletionContext context, string pathThroughLastSlash) diff --git a/src/Features/Core/Portable/Completion/Providers/Scripting/GlobalAssemblyCacheCompletionHelper.cs b/src/Features/Core/Portable/Completion/Providers/Scripting/GlobalAssemblyCacheCompletionHelper.cs index 103ffcbb2032b..bf0374424e591 100644 --- a/src/Features/Core/Portable/Completion/Providers/Scripting/GlobalAssemblyCacheCompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/Providers/Scripting/GlobalAssemblyCacheCompletionHelper.cs @@ -59,12 +59,9 @@ internal ImmutableArray GetItems(string directoryPath, Cancellat } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static IEnumerable GetAssemblyIdentities(string partialName) - { - return IOUtilities.PerformIO(() => GlobalAssemblyCache.Instance.GetAssemblyIdentities(partialName), - SpecializedCollections.EmptyEnumerable()); - } + => IOUtilities.PerformIO(() => GlobalAssemblyCache.Instance.GetAssemblyIdentities(partialName), []); } diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs index 63e7315b70148..3ed3ed6919891 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs @@ -23,10 +23,6 @@ public static CompletionItem Create( string inlineDescription, ImmutableArray additionalFilterTexts) { - var props = ImmutableArray.Create( - new KeyValuePair("Position", position.ToString()), - new KeyValuePair(SnippetIdentifierKey, snippetIdentifier)); - return CommonCompletionItem.Create( displayText: displayText, displayTextSuffix: displayTextSuffix, @@ -35,7 +31,9 @@ public static CompletionItem Create( // Adding a space after the identifier string that way it will always be sorted after a keyword. sortText: snippetIdentifier + " ", filterText: snippetIdentifier, - properties: props, + properties: [ + KeyValuePairUtil.Create("Position", position.ToString()), + KeyValuePairUtil.Create(SnippetIdentifierKey, snippetIdentifier)], isComplexTextEdit: true, inlineDescription: inlineDescription, rules: CompletionItemRules.Default) diff --git a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs index 1f3c651245e43..be34b7df9a37f 100644 --- a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs @@ -13,6 +13,8 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Tags; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion.Providers; @@ -49,13 +51,17 @@ private static CompletionItem CreateWorker( if (insertionText != null) { - builder.Add(new KeyValuePair(InsertionTextProperty, insertionText)); + builder.Add(KeyValuePairUtil.Create(InsertionTextProperty, insertionText)); } - builder.Add(new KeyValuePair("ContextPosition", contextPosition.ToString())); + builder.Add(KeyValuePairUtil.Create("ContextPosition", contextPosition.ToString())); AddSupportedPlatforms(builder, supportedPlatforms); symbolEncoder(symbols, builder); + tags = tags.NullToEmpty(); + if (symbols.All(symbol => symbol.IsObsolete()) && !tags.Contains(WellKnownTags.Deprecated)) + tags = tags.Add(WellKnownTags.Deprecated); + var firstSymbol = symbols[0]; var item = CommonCompletionItem.Create( displayText: displayText, @@ -75,17 +81,17 @@ private static CompletionItem CreateWorker( } private static void AddSymbolEncoding(IReadOnlyList symbols, ArrayBuilder> properties) - => properties.Add(new KeyValuePair("Symbols", EncodeSymbols(symbols))); + => properties.Add(KeyValuePairUtil.Create("Symbols", EncodeSymbols(symbols))); private static void AddSymbolInfo(IReadOnlyList symbols, ArrayBuilder> properties) { var symbol = symbols[0]; var isGeneric = symbol.GetArity() > 0; - properties.Add(new KeyValuePair("SymbolKind", ((int)symbol.Kind).ToString())); - properties.Add(new KeyValuePair("SymbolName", symbol.Name)); + properties.Add(KeyValuePairUtil.Create("SymbolKind", ((int)symbol.Kind).ToString())); + properties.Add(KeyValuePairUtil.Create("SymbolName", symbol.Name)); if (isGeneric) - properties.Add(new KeyValuePair("IsGeneric", isGeneric.ToString())); + properties.Add(KeyValuePairUtil.Create("IsGeneric", isGeneric.ToString())); } public static CompletionItem AddShouldProvideParenthesisCompletion(CompletionItem item) @@ -150,7 +156,7 @@ public static async Task> GetSymbolsAsync(CompletionItem } } - return symbols.ToImmutable(); + return symbols.ToImmutableAndClear(); } return []; @@ -220,8 +226,8 @@ private static void AddSupportedPlatforms(ArrayBuilder("InvalidProjects", string.Join(";", supportedPlatforms.InvalidProjects.Select(id => id.Id)))); - properties.Add(new KeyValuePair("CandidateProjects", string.Join(";", supportedPlatforms.CandidateProjects.Select(id => id.Id)))); + properties.Add(KeyValuePairUtil.Create("InvalidProjects", string.Join(";", supportedPlatforms.InvalidProjects.Select(id => id.Id)))); + properties.Add(KeyValuePairUtil.Create("CandidateProjects", string.Join(";", supportedPlatforms.CandidateProjects.Select(id => id.Id)))); } } diff --git a/src/Features/Core/Portable/Completion/Providers/XmlDocCommentCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/XmlDocCommentCompletionItem.cs index 85c33ff03a87b..a5031ba84843a 100644 --- a/src/Features/Core/Portable/Completion/Providers/XmlDocCommentCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/XmlDocCommentCompletionItem.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion.Providers; @@ -14,10 +15,6 @@ internal static class XmlDocCommentCompletionItem public static CompletionItem Create(string displayText, string beforeCaretText, string afterCaretText, CompletionItemRules rules) { - var props = ImmutableArray.Create( - new KeyValuePair(BeforeCaretText, beforeCaretText), - new KeyValuePair(AfterCaretText, afterCaretText)); - // Set isComplexTextEdit to be always true for simplicity, even // though we don't always need to make change outside the default // completion list Span. @@ -27,7 +24,9 @@ public static CompletionItem Create(string displayText, string beforeCaretText, displayText: displayText, displayTextSuffix: "", glyph: Glyph.Keyword, - properties: props, + properties: [ + KeyValuePairUtil.Create(BeforeCaretText, beforeCaretText), + KeyValuePairUtil.Create(AfterCaretText, afterCaretText)], rules: rules, isComplexTextEdit: true); } diff --git a/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs index 24b6101ffcff7..e0a363677cb8f 100644 --- a/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs @@ -463,7 +463,7 @@ await AddDocumentsToUpdateForProjectAsync( project, result, tupleFieldNames, cancellationToken).ConfigureAwait(false); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static async Task> GetDocumentsToUpdateForContainingProjectAsync( @@ -475,7 +475,7 @@ private static async Task> GetDocumentsToUpdate await AddDocumentsToUpdateForProjectAsync( project, result, tupleFieldNames, cancellationToken).ConfigureAwait(false); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static async Task AddDocumentsToUpdateForProjectAsync(Project project, ArrayBuilder result, ImmutableArray tupleFieldNames, CancellationToken cancellationToken) @@ -528,7 +528,7 @@ private static async Task> GetDocumentsToUpdate result.Add(new DocumentToUpdate(document, nodes)); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static ImmutableArray GetDocumentsToUpdateForContainingMember( diff --git a/src/Features/Core/Portable/Copilot/CopilotConstants.cs b/src/Features/Core/Portable/Copilot/CopilotConstants.cs new file mode 100644 index 0000000000000..7b6f866d9f06a --- /dev/null +++ b/src/Features/Core/Portable/Copilot/CopilotConstants.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Copilot; + +internal static class CopilotConstants +{ + public const int CopilotIconLogoId = 1; + public const int CopilotIconSparkleId = 2; + public const int CopilotIconSparkleBlueId = 3; + public static readonly Guid CopilotIconMonikerGuid = new("{4515B9BD-70A1-45FA-9545-D4536417C596}"); +} diff --git a/src/Features/Core/Portable/Copilot/Extensions.cs b/src/Features/Core/Portable/Copilot/Extensions.cs index 68b39f20e2021..00201400d7bbd 100644 --- a/src/Features/Core/Portable/Copilot/Extensions.cs +++ b/src/Features/Core/Portable/Copilot/Extensions.cs @@ -13,27 +13,23 @@ namespace Microsoft.CodeAnalysis.Copilot; internal static class Extensions { - public static async Task> GetCachedCopilotDiagnosticsAsync(this TextDocument document, TextSpan span, CancellationToken cancellationToken) + public static async Task> GetCachedCopilotDiagnosticsAsync(this TextDocument document, TextSpan? span, CancellationToken cancellationToken) { - var diagnostics = await document.GetCachedCopilotDiagnosticsAsync(cancellationToken).ConfigureAwait(false); - if (diagnostics.IsEmpty) + if (document is not Document sourceDocument) return []; - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - return diagnostics.WhereAsArray(diagnostic => span.IntersectsWith(diagnostic.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text))); - } - - public static async Task> GetCachedCopilotDiagnosticsAsync(this TextDocument document, CancellationToken cancellationToken) - { - if (document is not Document sourceDocument) - return ImmutableArray.Empty; + if (sourceDocument.GetLanguageService() is not { } optionsService || + await optionsService.IsCodeAnalysisOptionEnabledAsync().ConfigureAwait(false) is false) + { + return []; + } var copilotCodeAnalysisService = sourceDocument.GetLanguageService(); if (copilotCodeAnalysisService is null) - return ImmutableArray.Empty; + return []; var promptTitles = await copilotCodeAnalysisService.GetAvailablePromptTitlesAsync(sourceDocument, cancellationToken).ConfigureAwait(false); - var copilotDiagnostics = await copilotCodeAnalysisService.GetCachedDocumentDiagnosticsAsync(sourceDocument, promptTitles, cancellationToken).ConfigureAwait(false); + var copilotDiagnostics = await copilotCodeAnalysisService.GetCachedDocumentDiagnosticsAsync(sourceDocument, span, promptTitles, cancellationToken).ConfigureAwait(false); return copilotDiagnostics.SelectAsArray(d => DiagnosticData.Create(d, sourceDocument)); } } diff --git a/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs index 5d3931a773cf2..df8cbbdda3cad 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs @@ -16,17 +16,6 @@ namespace Microsoft.CodeAnalysis.Copilot; /// internal interface ICopilotCodeAnalysisService : ILanguageService { - /// - /// Returns true if we should show 'Refine using Copilot' hyperlink in the lightbulb - /// preview for code actions. - /// - Task IsRefineOptionEnabledAsync(); - - /// - /// Returns true if Copilot background code analysis feature is enabled. - /// - Task IsCodeAnalysisOptionEnabledAsync(); - /// /// Returns true if the Copilot service is available for making Copilot code analysis requests. /// @@ -61,7 +50,7 @@ internal interface ICopilotCodeAnalysisService : ILanguageService /// /// A prompt's title serves as the ID of the prompt, which can be used to selectively trigger analysis and retrive cached results. /// - Task> GetCachedDocumentDiagnosticsAsync(Document document, ImmutableArray promptTitles, CancellationToken cancellationToken); + Task> GetCachedDocumentDiagnosticsAsync(Document document, TextSpan? span, ImmutableArray promptTitles, CancellationToken cancellationToken); /// /// Method to start a Copilot refinement session on top of the changes between the given @@ -70,4 +59,15 @@ internal interface ICopilotCodeAnalysisService : ILanguageService /// which might be used to provide additional context to Copilot for the refinement session. /// Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken); + + /// + /// Method to fetch the on-the-fly documentation based on a a symbols + /// and the code for the symbols in . + /// + /// is a formatted string representation of an .
+ /// is a list of a code definitions from an . + /// is the language of the originating . + ///
+ ///
+ Task GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken); } diff --git a/src/Features/Core/Portable/Copilot/ICopilotOptionsService.cs b/src/Features/Core/Portable/Copilot/ICopilotOptionsService.cs new file mode 100644 index 0000000000000..62752f0adc64f --- /dev/null +++ b/src/Features/Core/Portable/Copilot/ICopilotOptionsService.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.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Copilot; + +/// +/// Provides options for Copilot features. +/// Created seperately from ICopilotCodeAnalysisService to avoid additiona; assembly load just for checking option values. +/// +internal interface ICopilotOptionsService : ILanguageService +{ + /// + /// Returns true if we should show 'Refine using Copilot' hyperlink in the lightbulb + /// preview for code actions. + /// + Task IsRefineOptionEnabledAsync(); + + /// + /// Returns true if Copilot background code analysis feature is enabled. + /// + Task IsCodeAnalysisOptionEnabledAsync(); + + /// + /// Returns true if Copilot on-the-fly docs feature is enabled. + /// + /// + Task IsOnTheFlyDocsOptionEnabledAsync(); +} diff --git a/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs b/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs index 96af9c567e86b..7e41ac5d80b2f 100644 --- a/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs +++ b/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs @@ -130,7 +130,7 @@ private async Task> FindMembersAsync( case 0: // If there were no name parts, then we don't have any members to return. // We only expect to hit this condition when the name provided does not parse. - return SpecializedCollections.EmptyList(); + return []; case 1: // They're just searching for a method name. Have to look through every type to find @@ -147,7 +147,7 @@ private async Task> FindMembersAsync( default: // They have a namespace or nested type qualified name. Walk up to the root namespace trying to match. var containers = await _solution.GetGlobalNamespacesAsync(cancellationToken).ConfigureAwait(false); - return FindMembers(containers, nameParts.ToArray()); + return FindMembers(containers, [.. nameParts]); } } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) diff --git a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs index 8fe77a1502baa..cbfd81891bc1e 100644 --- a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs +++ b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs @@ -249,7 +249,7 @@ private async Task ScanForDesignerCategoryUsageAsync( results.Add((data, projectVersion)); } - return results.ToImmutable(); + return results.ToImmutableAndClear(); async Task ComputeDesignerAttributeDataAsync( Project project, DocumentId documentId, string filePath, bool hasDesignerCategoryType) diff --git a/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs b/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs deleted file mode 100644 index 2e7cdd0704fde..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/AbstractHostDiagnosticUpdateSource.cs +++ /dev/null @@ -1,186 +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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.Collections; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics; - -/// -/// Diagnostic update source for reporting workspace host specific diagnostics, -/// which may not be related to any given project/document in the solution. -/// For example, these include diagnostics generated for exceptions from third party analyzers. -/// -internal abstract class AbstractHostDiagnosticUpdateSource : IDiagnosticUpdateSource -{ - private ImmutableDictionary> _analyzerHostDiagnosticsMap = - ImmutableDictionary>.Empty; - - public abstract Workspace Workspace { get; } - - public event EventHandler>? DiagnosticsUpdated; - public event EventHandler DiagnosticsCleared { add { } remove { } } - - public void RaiseDiagnosticsUpdated(ImmutableArray args) - { - if (!args.IsEmpty) - DiagnosticsUpdated?.Invoke(this, args); - } - - public void ReportAnalyzerDiagnostic(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, ProjectId? projectId) - { - // check whether we are reporting project specific diagnostic or workspace wide diagnostic - var solution = Workspace.CurrentSolution; - var project = projectId != null ? solution.GetProject(projectId) : null; - - // check whether project the diagnostic belong to still exist - if (projectId != null && project == null) - { - // project the diagnostic belong to already removed from the solution. - // ignore the diagnostic - return; - } - - ReportAnalyzerDiagnostic(analyzer, DiagnosticData.Create(solution, diagnostic, project), project); - } - - public void ReportAnalyzerDiagnostic(DiagnosticAnalyzer analyzer, DiagnosticData diagnosticData, Project? project) - { - var raiseDiagnosticsUpdated = true; - - var dxs = ImmutableInterlocked.AddOrUpdate(ref _analyzerHostDiagnosticsMap, - analyzer, - [diagnosticData], - (a, existing) => - { - var newDiags = existing.Add(diagnosticData); - raiseDiagnosticsUpdated = newDiags.Count > existing.Count; - return newDiags; - }); - - if (raiseDiagnosticsUpdated) - { - RaiseDiagnosticsUpdated([MakeCreatedArgs(analyzer, dxs, project)]); - } - } - - public void ClearAnalyzerReferenceDiagnostics(AnalyzerFileReference analyzerReference, string language, ProjectId projectId) - { - // Perf: if we don't have any diagnostics at all, just return right away; this avoids loading the analyzers - // which may have not been loaded if you didn't do too much in your session. - if (_analyzerHostDiagnosticsMap.Count == 0) - return; - - using var argsBuilder = TemporaryArray.Empty; - var analyzers = analyzerReference.GetAnalyzers(language); - AddArgsToClearAnalyzerDiagnostics(ref argsBuilder.AsRef(), analyzers, projectId); - RaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - } - - public void AddArgsToClearAnalyzerDiagnostics(ref TemporaryArray builder, ImmutableArray analyzers, ProjectId projectId) - { - foreach (var analyzer in analyzers) - { - AddArgsToClearAnalyzerDiagnostics(ref builder, analyzer, projectId); - } - } - - public void AddArgsToClearAnalyzerDiagnostics(ref TemporaryArray builder, ProjectId projectId) - { - foreach (var (analyzer, _) in _analyzerHostDiagnosticsMap) - { - AddArgsToClearAnalyzerDiagnostics(ref builder, analyzer, projectId); - } - } - - private void AddArgsToClearAnalyzerDiagnostics(ref TemporaryArray builder, DiagnosticAnalyzer analyzer, ProjectId projectId) - { - if (!_analyzerHostDiagnosticsMap.TryGetValue(analyzer, out var existing)) - { - return; - } - - // Check if analyzer is shared by analyzer references from different projects. - var sharedAnalyzer = existing.Contains(d => d.ProjectId != null && d.ProjectId != projectId); - if (sharedAnalyzer) - { - var newDiags = existing.Where(d => d.ProjectId != projectId).ToImmutableHashSet(); - if (newDiags.Count < existing.Count && - ImmutableInterlocked.TryUpdate(ref _analyzerHostDiagnosticsMap, analyzer, newDiags, existing)) - { - var project = Workspace.CurrentSolution.GetProject(projectId); - builder.Add(MakeRemovedArgs(analyzer, project)); - } - } - else if (ImmutableInterlocked.TryRemove(ref _analyzerHostDiagnosticsMap, analyzer, out existing)) - { - var project = Workspace.CurrentSolution.GetProject(projectId); - builder.Add(MakeRemovedArgs(analyzer, project)); - - if (existing.Any(d => d.ProjectId == null)) - { - builder.Add(MakeRemovedArgs(analyzer, project: null)); - } - } - } - - private DiagnosticsUpdatedArgs MakeCreatedArgs(DiagnosticAnalyzer analyzer, ImmutableHashSet items, Project? project) - { - return DiagnosticsUpdatedArgs.DiagnosticsCreated( - CreateId(analyzer, project), Workspace, project?.Solution, project?.Id, documentId: null, diagnostics: items.ToImmutableArray()); - } - - private DiagnosticsUpdatedArgs MakeRemovedArgs(DiagnosticAnalyzer analyzer, Project? project) - { - return DiagnosticsUpdatedArgs.DiagnosticsRemoved( - CreateId(analyzer, project), Workspace, project?.Solution, project?.Id, documentId: null); - } - - private HostArgsId CreateId(DiagnosticAnalyzer analyzer, Project? project) => new(this, analyzer, project?.Id); - - internal TestAccessor GetTestAccessor() - => new(this); - - internal readonly struct TestAccessor(AbstractHostDiagnosticUpdateSource abstractHostDiagnosticUpdateSource) - { - private readonly AbstractHostDiagnosticUpdateSource _abstractHostDiagnosticUpdateSource = abstractHostDiagnosticUpdateSource; - - internal ImmutableArray GetReportedDiagnostics() - => _abstractHostDiagnosticUpdateSource._analyzerHostDiagnosticsMap.Values.Flatten().ToImmutableArray(); - - internal ImmutableHashSet GetReportedDiagnostics(DiagnosticAnalyzer analyzer) - { - if (!_abstractHostDiagnosticUpdateSource._analyzerHostDiagnosticsMap.TryGetValue(analyzer, out var diagnostics)) - { - diagnostics = []; - } - - return diagnostics; - } - } - - private sealed class HostArgsId(AbstractHostDiagnosticUpdateSource source, DiagnosticAnalyzer analyzer, ProjectId? projectId) : AnalyzerUpdateArgsId(analyzer) - { - private readonly AbstractHostDiagnosticUpdateSource _source = source; - private readonly ProjectId? _projectId = projectId; - - public override bool Equals(object? obj) - { - if (obj is not HostArgsId other) - { - return false; - } - - return _source == other._source && _projectId == other._projectId && base.Equals(obj); - } - - public override int GetHashCode() - => Hash.Combine(_source.GetHashCode(), Hash.Combine(_projectId == null ? 1 : _projectId.GetHashCode(), base.GetHashCode())); - } -} diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerUpdateArgsId.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerUpdateArgsId.cs deleted file mode 100644 index bcc22fc1e1730..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerUpdateArgsId.cs +++ /dev/null @@ -1,22 +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.Common; - -namespace Microsoft.CodeAnalysis.Diagnostics; - -/// -/// Base type of a type that is used as for live diagnostic -/// -internal class AnalyzerUpdateArgsId : BuildToolId.Base, ISupportLiveUpdate -{ - public DiagnosticAnalyzer Analyzer => _Field1!; - - protected AnalyzerUpdateArgsId(DiagnosticAnalyzer analyzer) - : base(analyzer) - { - } - - public override string BuildTool => Analyzer.GetAnalyzerAssemblyName(); -} diff --git a/src/Features/Core/Portable/Diagnostics/BuildOnlyDiagnosticsService.cs b/src/Features/Core/Portable/Diagnostics/BuildOnlyDiagnosticsService.cs index acc6905019187..29c6b0831be2c 100644 --- a/src/Features/Core/Portable/Diagnostics/BuildOnlyDiagnosticsService.cs +++ b/src/Features/Core/Portable/Diagnostics/BuildOnlyDiagnosticsService.cs @@ -6,140 +6,158 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics; [ExportWorkspaceServiceFactory(typeof(IBuildOnlyDiagnosticsService), ServiceLayer.Default), Shared] -internal sealed class BuildOnlyDiagnosticsServiceFactory : IWorkspaceServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class BuildOnlyDiagnosticsServiceFactory( + IAsynchronousOperationListenerProvider asynchronousOperationProvider) : IWorkspaceServiceFactory { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public BuildOnlyDiagnosticsServiceFactory() - { - } - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new BuildOnlyDiagnosticsService(workspaceServices.Workspace); + => new BuildOnlyDiagnosticsService(workspaceServices.Workspace, asynchronousOperationProvider.GetListener(FeatureAttribute.Workspace)); - private sealed class BuildOnlyDiagnosticsService : IBuildOnlyDiagnosticsService + private sealed class BuildOnlyDiagnosticsService : IBuildOnlyDiagnosticsService, IDisposable { - private readonly object _gate = new(); + private readonly CancellationTokenSource _disposalTokenSource = new(); + private readonly AsyncBatchingWorkQueue _workQueue; + + private readonly SemaphoreSlim _gate = new(initialCount: 1); private readonly Dictionary> _documentDiagnostics = []; - private readonly Dictionary> _projectDiagnostics = []; - public BuildOnlyDiagnosticsService(Workspace workspace) + public BuildOnlyDiagnosticsService( + Workspace workspace, + IAsynchronousOperationListener asyncListener) { + _workQueue = new AsyncBatchingWorkQueue( + TimeSpan.Zero, + ProcessWorkQueueAsync, + asyncListener, + _disposalTokenSource.Token); workspace.WorkspaceChanged += OnWorkspaceChanged; } + public void Dispose() + => _disposalTokenSource.Dispose(); + private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) { + // Keep this switch in sync with the switch in ProcessWorkQueueAsync switch (e.Kind) { case WorkspaceChangeKind.SolutionAdded: case WorkspaceChangeKind.SolutionCleared: case WorkspaceChangeKind.SolutionReloaded: case WorkspaceChangeKind.SolutionRemoved: - ClearAllDiagnostics(); + // Cancel existing work as we're going to clear out everything anyways, so no point processing any + // document or project work. + _workQueue.AddWork(e, cancelExistingWork: true); break; - case WorkspaceChangeKind.ProjectReloaded: case WorkspaceChangeKind.ProjectRemoved: - ClearDiagnostics(e.OldSolution.GetProject(e.ProjectId)); - break; - case WorkspaceChangeKind.DocumentRemoved: case WorkspaceChangeKind.DocumentReloaded: case WorkspaceChangeKind.AdditionalDocumentRemoved: case WorkspaceChangeKind.AdditionalDocumentReloaded: case WorkspaceChangeKind.AnalyzerConfigDocumentRemoved: case WorkspaceChangeKind.AnalyzerConfigDocumentReloaded: - ClearDiagnostics(e.DocumentId); + _workQueue.AddWork(e); break; } } - public void AddBuildOnlyDiagnostics(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableArray diagnostics) + private async ValueTask ProcessWorkQueueAsync(ImmutableSegmentedList list, CancellationToken cancellationToken) { - lock (_gate) + foreach (var e in list) { - if (documentId != null) - { - _documentDiagnostics[documentId] = diagnostics; - } - else if (projectId != null) + // Keep this switch in sync with the switch in OnWorkspaceChanged + switch (e.Kind) { - _projectDiagnostics[projectId] = diagnostics; + case WorkspaceChangeKind.SolutionAdded: + case WorkspaceChangeKind.SolutionCleared: + case WorkspaceChangeKind.SolutionReloaded: + case WorkspaceChangeKind.SolutionRemoved: + await ClearAllDiagnosticsAsync(cancellationToken).ConfigureAwait(false); + break; + + case WorkspaceChangeKind.ProjectReloaded: + case WorkspaceChangeKind.ProjectRemoved: + await ClearDiagnosticsAsync(e.OldSolution.GetProject(e.ProjectId), cancellationToken).ConfigureAwait(false); + break; + + case WorkspaceChangeKind.DocumentRemoved: + case WorkspaceChangeKind.DocumentReloaded: + case WorkspaceChangeKind.AdditionalDocumentRemoved: + case WorkspaceChangeKind.AdditionalDocumentReloaded: + case WorkspaceChangeKind.AnalyzerConfigDocumentRemoved: + case WorkspaceChangeKind.AnalyzerConfigDocumentReloaded: + await ClearDiagnosticsAsync(e.DocumentId, cancellationToken).ConfigureAwait(false); + break; } } } - private void ClearAllDiagnostics() + public async Task AddBuildOnlyDiagnosticsAsync(DocumentId documentId, ImmutableArray diagnostics, CancellationToken cancellationToken) + { + using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + if (documentId != null) + _documentDiagnostics[documentId] = diagnostics; + } + } + + private async Task ClearAllDiagnosticsAsync(CancellationToken cancellationToken) { - lock (_gate) + using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { _documentDiagnostics.Clear(); - _projectDiagnostics.Clear(); } } - private void ClearDiagnostics(DocumentId? documentId) + private async Task ClearDiagnosticsAsync(DocumentId? documentId, CancellationToken cancellationToken) { if (documentId == null) return; - lock (_gate) + using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { _documentDiagnostics.Remove(documentId); } } - private void ClearDiagnostics(Project? project) + private async Task ClearDiagnosticsAsync(Project? project, CancellationToken cancellationToken) { if (project == null) return; - lock (_gate) + using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - _projectDiagnostics.Remove(project.Id); foreach (var documentId in project.DocumentIds) _documentDiagnostics.Remove(documentId); } } - public void ClearBuildOnlyDiagnostics(Solution solution, ProjectId? projectId, DocumentId? documentId) + public Task ClearBuildOnlyDiagnosticsAsync(Project project, DocumentId? documentId, CancellationToken cancellationToken) { if (documentId != null) - ClearDiagnostics(documentId); + return ClearDiagnosticsAsync(documentId, cancellationToken); else - ClearDiagnostics(solution.GetProject(projectId)); + return ClearDiagnosticsAsync(project, cancellationToken); } - public ImmutableArray GetBuildOnlyDiagnostics(DocumentId documentId) + public async ValueTask> GetBuildOnlyDiagnosticsAsync(DocumentId documentId, CancellationToken cancellationToken) { - lock (_gate) + using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - if (_documentDiagnostics.TryGetValue(documentId, out var diagnostics)) - { - return diagnostics; - } - - return []; - } - } - - public ImmutableArray GetBuildOnlyDiagnostics(ProjectId projectId) - { - lock (_gate) - { - if (_projectDiagnostics.TryGetValue(projectId, out var diagnostics)) - { - return diagnostics; - } - - return []; + return _documentDiagnostics.TryGetValue(documentId, out var diagnostics) ? diagnostics : []; } } } diff --git a/src/Features/Core/Portable/Diagnostics/BuildToolId.cs b/src/Features/Core/Portable/Diagnostics/BuildToolId.cs deleted file mode 100644 index 2b282e4382a9a..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/BuildToolId.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics; - -/// -/// Support ErrorSource information. -/// -internal abstract class BuildToolId -{ - public abstract string BuildTool { get; } - - internal abstract class Base(T? field) : BuildToolId - { - protected readonly T? _Field1 = field; - - public override bool Equals(object? obj) - { - if (obj is not Base other) - { - return false; - } - - return object.Equals(_Field1, other._Field1); - } - - public override int GetHashCode() - => _Field1?.GetHashCode() ?? 0; - } - - internal abstract class Base(T1? field1, T2? field2) : Base(field2) - { - private readonly T1? _Field2 = field1; - - public override bool Equals(object? obj) - { - if (obj is not Base other) - { - return false; - } - - return object.Equals(_Field2, other._Field2) && base.Equals(other); - } - - public override int GetHashCode() - => Hash.Combine(_Field2?.GetHashCode() ?? 0, base.GetHashCode()); - } -} diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticsUpdatedArgs.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticsUpdatedArgs.cs index 7430819d4befc..10396b0d0208c 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticsUpdatedArgs.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticsUpdatedArgs.cs @@ -5,52 +5,56 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using Microsoft.CodeAnalysis.Common; namespace Microsoft.CodeAnalysis.Diagnostics; -internal sealed class DiagnosticsUpdatedArgs : UpdatedEventArgs +internal sealed class DiagnosticsUpdatedArgs { public readonly DiagnosticsUpdatedKind Kind; public readonly Solution? Solution; + + /// + /// this update is associated with, or . + /// + public readonly ProjectId? ProjectId; + + /// + /// this update is associated with, or . + /// + public readonly DocumentId? DocumentId; public readonly ImmutableArray Diagnostics; private DiagnosticsUpdatedArgs( - object id, - Workspace workspace, Solution? solution, ProjectId? projectId, DocumentId? documentId, ImmutableArray diagnostics, DiagnosticsUpdatedKind kind) - : base(id, workspace, projectId, documentId) { Debug.Assert(diagnostics.All(d => d.ProjectId == projectId && d.DocumentId == documentId)); Debug.Assert(kind != DiagnosticsUpdatedKind.DiagnosticsRemoved || diagnostics.IsEmpty); Solution = solution; + ProjectId = projectId; + DocumentId = documentId; Kind = kind; Diagnostics = diagnostics; } public static DiagnosticsUpdatedArgs DiagnosticsCreated( - object id, - Workspace workspace, - Solution? solution, + Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableArray diagnostics) { - return new DiagnosticsUpdatedArgs(id, workspace, solution, projectId, documentId, diagnostics, DiagnosticsUpdatedKind.DiagnosticsCreated); + return new DiagnosticsUpdatedArgs(solution, projectId, documentId, diagnostics, DiagnosticsUpdatedKind.DiagnosticsCreated); } public static DiagnosticsUpdatedArgs DiagnosticsRemoved( - object id, - Workspace workspace, Solution? solution, ProjectId? projectId, DocumentId? documentId) { - return new DiagnosticsUpdatedArgs(id, workspace, solution, projectId, documentId, [], DiagnosticsUpdatedKind.DiagnosticsRemoved); + return new DiagnosticsUpdatedArgs(solution, projectId, documentId, [], DiagnosticsUpdatedKind.DiagnosticsRemoved); } } diff --git a/src/Features/Core/Portable/Diagnostics/IBuildOnlyDiagnosticsService.cs b/src/Features/Core/Portable/Diagnostics/IBuildOnlyDiagnosticsService.cs index 4230d17fb9539..59ef009410c78 100644 --- a/src/Features/Core/Portable/Diagnostics/IBuildOnlyDiagnosticsService.cs +++ b/src/Features/Core/Portable/Diagnostics/IBuildOnlyDiagnosticsService.cs @@ -3,6 +3,8 @@ // 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; namespace Microsoft.CodeAnalysis.Diagnostics; @@ -13,11 +15,9 @@ namespace Microsoft.CodeAnalysis.Diagnostics; ///
internal interface IBuildOnlyDiagnosticsService : IWorkspaceService { - void AddBuildOnlyDiagnostics(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableArray diagnostics); + Task AddBuildOnlyDiagnosticsAsync(DocumentId documentId, ImmutableArray diagnostics, CancellationToken cancellationToken); - void ClearBuildOnlyDiagnostics(Solution solution, ProjectId? projectId, DocumentId? documentId); + Task ClearBuildOnlyDiagnosticsAsync(Project project, DocumentId? documentId, CancellationToken cancellationToken); - ImmutableArray GetBuildOnlyDiagnostics(DocumentId documentId); - - ImmutableArray GetBuildOnlyDiagnostics(ProjectId projectId); + ValueTask> GetBuildOnlyDiagnosticsAsync(DocumentId documentId, CancellationToken cancellationToken); } diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index 5c85d2e600442..8fadcb3bc2123 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -9,12 +9,11 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Diagnostics; -// TODO: Remove all optional parameters from IDiagnosticAnalyzerService -// Tracked with https://github.com/dotnet/roslyn/issues/67434 internal interface IDiagnosticAnalyzerService { public IGlobalOptionService GlobalOptions { get; } @@ -25,10 +24,9 @@ internal interface IDiagnosticAnalyzerService DiagnosticAnalyzerInfoCache AnalyzerInfoCache { get; } /// - /// Re-analyze given projects and documents. If both and are null, - /// then re-analyzes the entire for the given . + /// Re-analyze all projects and documents. This will cause an LSP diagnostic refresh request to be sent. /// - void Reanalyze(Workspace workspace, IEnumerable? projectIds, IEnumerable? documentIds, bool highPriority); + void RequestDiagnosticRefresh(); /// /// Get diagnostics currently stored in the source. returned diagnostic might be out-of-date if solution has changed but analyzer hasn't run for the new solution. @@ -51,22 +49,6 @@ internal interface IDiagnosticAnalyzerService /// Cancellation token. Task> GetCachedDiagnosticsAsync(Workspace workspace, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); - /// - /// Get diagnostics for the given solution. all diagnostics returned should be up-to-date with respect to the given solution. - /// - /// Solution to fetch diagnostics for. - /// Optional project to scope the returned diagnostics. - /// Optional document to scope the returned diagnostics. - /// Indicates if diagnostics suppressed in source via pragmas and SuppressMessageAttributes should be returned. - /// - /// Indicates if non-local document diagnostics must be returned. - /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback OR - /// in a different file from which the callback was made. Entire project must be analyzed to get the - /// complete set of non-local document diagnostics. - /// - /// Cancellation token. - Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); - /// /// Force analyzes the given project by running all applicable analyzers on the project and caching the reported analyzer diagnostics. /// @@ -95,11 +77,13 @@ internal interface IDiagnosticAnalyzerService /// complete set of non-local document diagnostics. /// /// Cancellation token. - Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); + Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocumentIds, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// - /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. - /// Note that this method doesn't return any document diagnostics. Use to also fetch those. + /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from + /// the given solution. all diagnostics returned should be up-to-date with respect to the given solution. Note that + /// this method doesn't return any document diagnostics. Use to also fetch + /// those. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -201,4 +185,12 @@ public static Task> GetDiagnosticsForSpanAsync(th includeCompilerDiagnostics: true, includeSuppressedDiagnostics, priorityProvider, addOperationScope, diagnosticKind, isExplicit, cancellationToken); } + + public static Task> GetDiagnosticsForIdsAsync( + this IDiagnosticAnalyzerService service, Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + { + return service.GetDiagnosticsForIdsAsync( + solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocumentIds: null, + includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + } } diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticUpdateSource.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticUpdateSource.cs deleted file mode 100644 index eb75cb6bd063b..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticUpdateSource.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.CodeAnalysis.Diagnostics; - -/// -/// Implement this to participate in diagnostic service framework as one of diagnostic update source -/// -internal interface IDiagnosticUpdateSource -{ - /// - /// Raise this when new diagnostics are found - /// - event EventHandler> DiagnosticsUpdated; - - /// - /// Raise this when all diagnostics reported from this update source has cleared - /// - event EventHandler DiagnosticsCleared; -} diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticUpdateSourceRegistrationService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticUpdateSourceRegistrationService.cs deleted file mode 100644 index 8d8d929ed5d82..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticUpdateSourceRegistrationService.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -namespace Microsoft.CodeAnalysis.Diagnostics; - -/// -/// A service that let people to register new IDiagnosticUpdateSource -/// -internal interface IDiagnosticUpdateSourceRegistrationService -{ - /// - /// Register new IDiagnosticUpdateSource - /// - /// Currently, it doesn't support unregister since our event is asynchronous and unregistering source that deal with asynchronous event is not straight forward. - /// - void Register(IDiagnosticUpdateSource source); -} diff --git a/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs b/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs deleted file mode 100644 index 3458e3bcf33c0..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.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; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics; - -internal class LiveDiagnosticUpdateArgsId : AnalyzerUpdateArgsId -{ - private string? _buildTool; - - public readonly object ProjectOrDocumentId; - public readonly AnalysisKind Kind; - - public LiveDiagnosticUpdateArgsId(DiagnosticAnalyzer analyzer, object projectOrDocumentId, AnalysisKind kind) - : base(analyzer) - { - Contract.ThrowIfNull(projectOrDocumentId); - - ProjectOrDocumentId = projectOrDocumentId; - Kind = kind; - } - - public override string BuildTool => _buildTool ??= ComputeBuildTool(); - - private string ComputeBuildTool() - => Analyzer.IsBuiltInAnalyzer() ? PredefinedBuildTools.Live : Analyzer.GetAnalyzerAssemblyName(); - - public override bool Equals(object? obj) - { - if (obj is not LiveDiagnosticUpdateArgsId other) - { - return false; - } - - return Kind == other.Kind && Equals(ProjectOrDocumentId, other.ProjectOrDocumentId) && base.Equals(obj); - } - - public override int GetHashCode() - => Hash.Combine(ProjectOrDocumentId, Hash.Combine((int)Kind, base.GetHashCode())); -} diff --git a/src/Features/Core/Portable/Diagnostics/PredefinedBuildTools.cs b/src/Features/Core/Portable/Diagnostics/PredefinedBuildTools.cs deleted file mode 100644 index 93de528e384e6..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/PredefinedBuildTools.cs +++ /dev/null @@ -1,13 +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.Diagnostics; - -internal static class PredefinedBuildTools -{ - public static readonly string Build = FeaturesResources.Compiler2; - public static readonly string Live = FeaturesResources.Live; -} diff --git a/src/Features/Core/Portable/Diagnostics/PredefinedDiagnosticProviderNames.cs b/src/Features/Core/Portable/Diagnostics/PredefinedDiagnosticProviderNames.cs deleted file mode 100644 index 52f17c50bec1e..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/PredefinedDiagnosticProviderNames.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 - -namespace Microsoft.CodeAnalysis.Diagnostics; - -internal static class PredefinedDiagnosticProviderNames -{ -#if false - public const string AddMissingReference = "Add Missing Reference"; - public const string AddUsingOrImport = "Add Using or Import"; - public const string FullyQualify = "Fully Qualify"; - public const string FixIncorrectExitContinue = "Fix Incorrect Exit Continue"; - public const string GenerateConstructor = "Generate Constructor"; - public const string GenerateEndConstruct = "Generate End Construct"; - public const string GenerateEnumMember = "Generate Enum Member"; - public const string GenerateEvent = "Generate Event"; - public const string GenerateVariable = "Generate Variable"; - public const string GenerateMethod = "Generate Method"; - public const string GenerateType = "Generate Type"; - public const string ImplementAbstractClass = "Implement Abstract Class"; - public const string MoveToTopOfFile = "Move To Top Of File"; -#endif - public const string AddUsingOrImport = "Add Using or Import"; - public const string ImplementInterface = "Implement Interface"; - public const string RemoveUnnecessaryCast = "Remove Unnecessary Casts"; - public const string RemoveUnnecessaryImports = "Remove Unnecessary Usings or Imports"; - public const string RenameTracking = "Rename Tracking"; - public const string SimplifyNames = "Simplify Names"; - public const string SpellCheck = "Spell Check"; -} diff --git a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs index f25de00436c7c..f4075ce374447 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs @@ -63,7 +63,9 @@ public async Task> GetDocumentHighlightsAsync private async Task> GetDocumentHighlightsInCurrentProcessAsync( Document document, int position, IImmutableSet documentsToSearch, HighlightingOptions options, CancellationToken cancellationToken) { - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + // Document highlights are not impacted by nullable analysis. Get a semantic model with nullability disabled to + // lower the amount of work we need to do here. + var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); var result = TryGetEmbeddedLanguageHighlights(document, semanticModel, position, options, cancellationToken); if (!result.IsDefaultOrEmpty) return result; @@ -263,13 +265,11 @@ private static async Task> CreateSpansAsync( await AddLocationSpanAsync(location, solution, spanSet, tagMap, HighlightSpanKind.Reference, cancellationToken).ConfigureAwait(false); } - using var _1 = ArrayBuilder.GetInstance(tagMap.Count, out var list); + var list = new FixedSizeArrayBuilder(tagMap.Count); foreach (var kvp in tagMap) - { list.Add(new DocumentHighlights(kvp.Key, [.. kvp.Value])); - } - return list.ToImmutableAndClear(); + return list.MoveToImmutable(); } private static bool ShouldIncludeDefinition(ISymbol symbol) diff --git a/src/Features/Core/Portable/DocumentHighlighting/HighlightingOptions.cs b/src/Features/Core/Portable/DocumentHighlighting/HighlightingOptions.cs index 867e8dbca91ff..9a73a3bbc7044 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/HighlightingOptions.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/HighlightingOptions.cs @@ -7,14 +7,11 @@ namespace Microsoft.CodeAnalysis.DocumentHighlighting; [DataContract] -internal readonly record struct HighlightingOptions +internal readonly record struct HighlightingOptions() { [DataMember] public bool HighlightRelatedRegexComponentsUnderCursor { get; init; } = true; [DataMember] public bool HighlightRelatedJsonComponentsUnderCursor { get; init; } = true; - - public HighlightingOptions() - { - } + [DataMember] public bool FrozenPartialSemantics { get; init; } public static HighlightingOptions Default = new(); } diff --git a/src/Features/Core/Portable/DocumentationComments/AbstractDocumentationCommentFormattingService.cs b/src/Features/Core/Portable/DocumentationComments/AbstractDocumentationCommentFormattingService.cs index 3b64e398908ca..71d796686baf0 100644 --- a/src/Features/Core/Portable/DocumentationComments/AbstractDocumentationCommentFormattingService.cs +++ b/src/Features/Core/Portable/DocumentationComments/AbstractDocumentationCommentFormattingService.cs @@ -490,7 +490,7 @@ private static void AppendTextFromAttribute(FormatterState state, XAttribute att ? attribute.Value : null; var navigationHint = navigationTarget; - state.AppendParts(SpecializedCollections.SingletonEnumerable(new TaggedText(displayKind, text, style, navigationTarget, navigationHint))); + state.AppendParts([new TaggedText(displayKind, text, style, navigationTarget, navigationHint)]); } } @@ -514,8 +514,7 @@ internal static IEnumerable CrefToSymbolDisplayParts( } // if any of that fails fall back to just displaying the raw text - return SpecializedCollections.SingletonEnumerable( - new SymbolDisplayPart(kind, symbol: null, text: TrimCrefPrefix(crefValue))); + return [new SymbolDisplayPart(kind, symbol: null, text: TrimCrefPrefix(crefValue))]; } internal static IEnumerable TypeParameterRefToSymbolDisplayParts( @@ -535,8 +534,7 @@ internal static IEnumerable TypeParameterRefToSymbolDisplayPa } // if any of that fails fall back to just displaying the raw text - return SpecializedCollections.SingletonEnumerable( - new SymbolDisplayPart(SymbolDisplayPartKind.TypeParameterName, symbol: null, text: TrimCrefPrefix(crefValue))); + return [new SymbolDisplayPart(SymbolDisplayPartKind.TypeParameterName, symbol: null, text: TrimCrefPrefix(crefValue))]; } private static string TrimCrefPrefix(string value) diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index a903b17faaeb8..91e07ddb08d02 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -511,7 +511,7 @@ public async Task AnalyzeDocumentAsync( Project oldProject, AsyncLazy lazyOldActiveStatementMap, Document newDocument, - ImmutableArray newActiveStatementSpans, + ImmutableArray newActiveStatementSpans, AsyncLazy lazyCapabilities, CancellationToken cancellationToken) { @@ -778,13 +778,12 @@ private void AnalyzeUnchangedActiveMemberBodies( Match topMatch, SourceText newText, ImmutableArray oldActiveStatements, - ImmutableArray newActiveStatementSpans, + ImmutableArray newActiveStatementSpans, [In, Out] ImmutableArray.Builder newActiveStatements, [In, Out] ImmutableArray>.Builder newExceptionRegions, CancellationToken cancellationToken) { Debug.Assert(!newActiveStatementSpans.IsDefault); - Debug.Assert(newActiveStatementSpans.IsEmpty || oldActiveStatements.Length == newActiveStatementSpans.Length); Debug.Assert(oldActiveStatements.Length == newActiveStatements.Count); Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Count); @@ -824,7 +823,7 @@ private void AnalyzeUnchangedActiveMemberBodies( // We seed the method body matching algorithm with tracking spans (unless they were deleted) // to get precise matching. - if (TryGetTrackedStatement(newActiveStatementSpans, i, newText, newBody, out var trackedStatement, out var trackedStatementPart)) + if (TryGetTrackedStatement(newActiveStatementSpans, oldActiveStatements[i].Statement.Id, newText, newBody, out var trackedStatement, out var trackedStatementPart)) { // Adjust for active statements that cover more than the old member span. // For example, C# variable declarators that represent field initializers: @@ -833,7 +832,7 @@ private void AnalyzeUnchangedActiveMemberBodies( // The tracking span might have been moved outside of lambda. // It is not an error to move the statement - we just ignore it. - var oldEnclosingLambdaBody = FindEnclosingLambdaBody(oldBody.EncompassingAncestor, oldMember.FindToken(adjustedOldStatementStart).Parent!); + var oldEnclosingLambdaBody = FindEnclosingLambdaBody(oldBody.EncompassingAncestor, oldBody.EncompassingAncestor.FindToken(adjustedOldStatementStart).Parent!); var newEnclosingLambdaBody = FindEnclosingLambdaBody(newBody.EncompassingAncestor, trackedStatement); if (oldEnclosingLambdaBody == newEnclosingLambdaBody) { @@ -932,7 +931,7 @@ private void AnalyzeChangedMemberBody( bool isMemberReplaced, Match topMatch, ImmutableArray oldActiveStatements, - ImmutableArray newActiveStatementSpans, + ImmutableArray newActiveStatementSpans, EditAndContinueCapabilitiesGrantor capabilities, [Out] ImmutableArray.Builder newActiveStatements, [Out] ImmutableArray>.Builder newExceptionRegions, @@ -948,7 +947,7 @@ private void AnalyzeChangedMemberBody( var diagnosticContext = CreateDiagnosticContext(diagnostics, oldMember, newMember, newDeclaration, newModel, topMatch); - var activeStatementIndices = oldMemberBody?.GetOverlappingActiveStatements(oldActiveStatements)?.ToArray() ?? []; + var activeStatementIndices = oldMemberBody?.GetOverlappingActiveStatementIndices(oldActiveStatements)?.ToArray() ?? []; if (isMemberReplaced && !activeStatementIndices.IsEmpty()) { @@ -1030,7 +1029,7 @@ private void AnalyzeChangedMemberBody( SyntaxNode? trackedNode = null; - if (TryGetTrackedStatement(newActiveStatementSpans, activeStatementIndex, newText, newMemberBody, out var newStatementSyntax, out var _)) + if (TryGetTrackedStatement(newActiveStatementSpans, oldActiveStatements[activeStatementIndex].Statement.Id, newText, newMemberBody, out var newStatementSyntax, out var _)) { var newEnclosingLambdaBody = FindEnclosingLambdaBody(newMemberBody.EncompassingAncestor, newStatementSyntax); @@ -1282,18 +1281,13 @@ private void AnalyzeChangedMemberBody( } } - private static bool TryGetTrackedStatement(ImmutableArray activeStatementSpans, int index, SourceText text, MemberBody body, [NotNullWhen(true)] out SyntaxNode? trackedStatement, out int trackedStatementPart) + private static bool TryGetTrackedStatement(ImmutableArray activeStatementSpans, ActiveStatementId id, SourceText text, MemberBody body, [NotNullWhen(true)] out SyntaxNode? trackedStatement, out int trackedStatementPart) { trackedStatement = null; trackedStatementPart = -1; - // Active statements are not tracked in this document (e.g. the file is closed). - if (activeStatementSpans.IsEmpty) - { - return false; - } - - var trackedLineSpan = activeStatementSpans[index]; + // Active statement span not tracked or tracking span has been lost. + var trackedLineSpan = activeStatementSpans.FirstOrDefault(static (s, id) => s.Id == id, id).LineSpan; if (trackedLineSpan == default) { return false; @@ -2432,7 +2426,7 @@ private async Task> AnalyzeSemanticsAsync( EditScript editScript, IReadOnlyDictionary editMap, ImmutableArray oldActiveStatements, - ImmutableArray newActiveStatementSpans, + ImmutableArray newActiveStatementSpans, IReadOnlyList<(SyntaxNode OldNode, SyntaxNode NewNode, TextSpan DiagnosticSpan)> triviaEdits, Project oldProject, Document? oldDocument, @@ -2573,7 +2567,7 @@ private async Task> AnalyzeSemanticsAsync( { if (processedSymbols.Add(newContainingType)) { - if (capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) + if (capabilities.GrantNewTypeDefinition(containingType)) { semanticEdits.Add(SemanticEditInfo.CreateReplace(containingTypeSymbolKey, IsPartialTypeEdit(oldContainingType, newContainingType, oldTree, newTree) ? containingTypeSymbolKey : null)); @@ -2607,7 +2601,7 @@ private async Task> AnalyzeSemanticsAsync( // https://github.com/dotnet/roslyn/issues/54881 diagnosticContext.Report(RudeEditKind.ChangingTypeParameters, cancellationToken); } - else if (!capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) + else if (!capabilities.GrantNewTypeDefinition(oldType)) { diagnosticContext.Report(RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, cancellationToken); } @@ -2889,7 +2883,7 @@ newSymbol is IPropertySymbol newProperty && // therefore inserting the $ type Contract.ThrowIfFalse(newSymbol is INamedTypeSymbol || IsGlobalMain(newSymbol)); - if (!capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) + if (!capabilities.GrantNewTypeDefinition((newSymbol as INamedTypeSymbol) ?? newSymbol.ContainingType)) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.InsertNotSupportedByRuntime, @@ -3016,7 +3010,7 @@ void ReportDeletedMemberActiveStatementsRudeEdits() return; } - var activeStatementIndices = oldBody.GetOverlappingActiveStatements(oldActiveStatements); + var activeStatementIndices = oldBody.GetOverlappingActiveStatementIndices(oldActiveStatements); if (!activeStatementIndices.Any()) { return; @@ -3211,7 +3205,7 @@ IFieldSymbol or { if (processedSymbols.Add(newContainingType)) { - if (capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) + if (capabilities.GrantNewTypeDefinition(newContainingType)) { var oldContainingTypeKey = SymbolKey.Create(oldContainingType, cancellationToken); semanticEdits.Add(SemanticEditInfo.CreateReplace(oldContainingTypeKey, @@ -3253,7 +3247,7 @@ IFieldSymbol or // We need to provide syntax map to the compiler if the member is active (see member update above): var isActiveMember = - oldBody.GetOverlappingActiveStatements(oldActiveStatements).Any() || + oldBody.GetOverlappingActiveStatementIndices(oldActiveStatements).Any() || IsStateMachineMethod(oldDeclaration) || ContainsLambda(oldBody); @@ -3815,7 +3809,7 @@ void AddDelete(ISymbol? symbol) foreach (var (_, indices) in deletedTypes) indices.Free(); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private static bool IsReloadable(INamedTypeSymbol type) @@ -3895,7 +3889,7 @@ private void ReportMemberOrLambdaBodyUpdateRudeEdits( if (!oldStateMachineInfo.IsStateMachine && newStateMachineInfo.IsStateMachine && - !capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) + !capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation)) { // Adding a state machine, either for async or iterator, will require creating a new helper class // so is a rude edit if the runtime doesn't support it @@ -5690,9 +5684,11 @@ bool CanAddNewLambda(SyntaxNode newLambda, LambdaBody newLambdaBody1, LambdaBody } } - // If the old verison of the method had any lambdas the nwe know a closure type exists and a new one isn't needed. + // If the old version of the method had any lambdas then we know a closure type exists and a new one isn't needed. // We also know that adding a local function won't create a new closure type. // Otherwise, we assume a new type is needed. + // We also assume that the closure type does not implement an interface explicitly, + // so we do not need AddExplicitInterfaceImplementation capability. if (!oldHasLambdas && !isLocalFunction) { diff --git a/src/Features/Core/Portable/EditAndContinue/ActiveStatement.cs b/src/Features/Core/Portable/EditAndContinue/ActiveStatement.cs index 19701f66dfe94..e30af136737aa 100644 --- a/src/Features/Core/Portable/EditAndContinue/ActiveStatement.cs +++ b/src/Features/Core/Portable/EditAndContinue/ActiveStatement.cs @@ -18,7 +18,7 @@ internal sealed class ActiveStatement /// /// Ordinal of the active statement within the set of all active statements. /// - public readonly int Ordinal; + public readonly ActiveStatementId Id; /// /// The instruction of the active statement that is being executed. @@ -37,11 +37,9 @@ internal sealed class ActiveStatement /// public readonly ActiveStatementFlags Flags; - public ActiveStatement(int ordinal, ActiveStatementFlags flags, SourceFileSpan span, ManagedInstructionId instructionId) + public ActiveStatement(ActiveStatementId id, ActiveStatementFlags flags, SourceFileSpan span, ManagedInstructionId instructionId) { - Debug.Assert(ordinal >= 0); - - Ordinal = ordinal; + Id = id; Flags = flags; FileSpan = span; InstructionId = instructionId; @@ -54,10 +52,10 @@ public ActiveStatement WithSpan(LinePositionSpan span) => WithFileSpan(FileSpan.WithSpan(span)); public ActiveStatement WithFileSpan(SourceFileSpan span) - => new(Ordinal, Flags, span, InstructionId); + => new(Id, Flags, span, InstructionId); public ActiveStatement WithFlags(ActiveStatementFlags flags) - => new(Ordinal, flags, FileSpan, InstructionId); + => new(Id, flags, FileSpan, InstructionId); public LinePositionSpan Span => FileSpan.Span; @@ -90,5 +88,5 @@ public bool IsStale => (Flags & ActiveStatementFlags.Stale) != 0; private string GetDebuggerDisplay() - => $"{Ordinal}: {Span}"; + => $"{Id}: {Span}"; } diff --git a/src/Features/Core/Portable/EditAndContinue/ActiveStatementId.cs b/src/Features/Core/Portable/EditAndContinue/ActiveStatementId.cs index ff18daeb358e4..5aa4a993be01e 100644 --- a/src/Features/Core/Portable/EditAndContinue/ActiveStatementId.cs +++ b/src/Features/Core/Portable/EditAndContinue/ActiveStatementId.cs @@ -2,12 +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.Runtime.Serialization; namespace Microsoft.CodeAnalysis.EditAndContinue; -internal readonly struct ActiveStatementId(DocumentId documentId, int ordinal) -{ - public readonly DocumentId DocumentId = documentId; - public readonly int Ordinal = ordinal; -} +[DataContract] +internal readonly record struct ActiveStatementId([property: DataMember(Order = 0)] int Ordinal); + diff --git a/src/Features/Core/Portable/EditAndContinue/ActiveStatementLineSpan.cs b/src/Features/Core/Portable/EditAndContinue/ActiveStatementLineSpan.cs new file mode 100644 index 0000000000000..dd032bc414339 --- /dev/null +++ b/src/Features/Core/Portable/EditAndContinue/ActiveStatementLineSpan.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.EditAndContinue; + +/// +/// Represents location of an active statement tracked by the client editor. +/// +/// The corresponding . +/// Line span in the mapped document. +[DataContract] +internal readonly record struct ActiveStatementLineSpan( + [property: DataMember(Order = 0)] ActiveStatementId Id, + [property: DataMember(Order = 1)] LinePositionSpan LineSpan); diff --git a/src/Features/Core/Portable/EditAndContinue/ActiveStatementSpan.cs b/src/Features/Core/Portable/EditAndContinue/ActiveStatementSpan.cs index 105af64b2ca55..46e64067ad934 100644 --- a/src/Features/Core/Portable/EditAndContinue/ActiveStatementSpan.cs +++ b/src/Features/Core/Portable/EditAndContinue/ActiveStatementSpan.cs @@ -2,67 +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. -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; -using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.EditAndContinue; /// /// Represents a span of an active statement tracked by the client editor. /// +/// The corresponding . +/// Line span in the mapped document. +/// Flags. +/// +/// The id of the unmapped document where the source of the active statement is and from where the statement might be mapped to via #line directive. +/// Null if unknown (not determined yet). +/// [DataContract] -internal readonly struct ActiveStatementSpan : IEquatable -{ - /// - /// The corresponding . - /// - [DataMember(Order = 0)] - public readonly int Ordinal; - - /// - /// Line span in the mapped document. - /// - [DataMember(Order = 1)] - public readonly LinePositionSpan LineSpan; - - /// - /// Flags. - /// - [DataMember(Order = 2)] - public readonly ActiveStatementFlags Flags; - - /// - /// The id of the unmapped document where the source of the active statement is and from where the statement might be mapped to via #line directive. - /// Null if unknown (not determined yet). - /// - [DataMember(Order = 3)] - public readonly DocumentId? UnmappedDocumentId; - - public ActiveStatementSpan(int ordinal, LinePositionSpan lineSpan, ActiveStatementFlags flags, DocumentId? unmappedDocumentId) - { - Debug.Assert(ordinal >= 0); - - Ordinal = ordinal; - LineSpan = lineSpan; - Flags = flags; - UnmappedDocumentId = unmappedDocumentId; - } - - public override bool Equals(object? obj) - => obj is ActiveStatementSpan other && Equals(other); - - public bool Equals(ActiveStatementSpan other) - => Ordinal.Equals(other.Ordinal) && - LineSpan.Equals(other.LineSpan) && - Flags == other.Flags && - UnmappedDocumentId == other.UnmappedDocumentId; - - public override int GetHashCode() - => Hash.Combine(Ordinal, Hash.Combine(LineSpan.GetHashCode(), Hash.Combine(UnmappedDocumentId, (int)Flags))); -} +internal readonly record struct ActiveStatementSpan( + [property: DataMember(Order = 0)] ActiveStatementId Id, + [property: DataMember(Order = 1)] LinePositionSpan LineSpan, + [property: DataMember(Order = 2)] ActiveStatementFlags Flags, + [property: DataMember(Order = 3)] DocumentId? UnmappedDocumentId = null); diff --git a/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs b/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs index f2f503d49ee6d..b5069bc9a549e 100644 --- a/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs +++ b/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs @@ -25,8 +25,8 @@ internal sealed class ActiveStatementsMap public static readonly Comparer Comparer = Comparer.Create((x, y) => x.FileSpan.Start.CompareTo(y.FileSpan.Start)); - private static readonly Comparer<(ManagedActiveStatementDebugInfo, SourceFileSpan, int)> s_infoSpanComparer = - Comparer<(ManagedActiveStatementDebugInfo, SourceFileSpan span, int)>.Create((x, y) => x.span.Start.CompareTo(y.span.Start)); + private static readonly Comparer<(ManagedActiveStatementDebugInfo, SourceFileSpan, ActiveStatementId)> s_infoSpanComparer = + Comparer<(ManagedActiveStatementDebugInfo, SourceFileSpan span, ActiveStatementId)>.Create((x, y) => x.span.Start.CompareTo(y.span.Start)); /// /// Groups active statements by document path as listed in the PDB. @@ -60,7 +60,7 @@ public static ActiveStatementsMap Create( ImmutableArray debugInfos, ImmutableDictionary> remapping) { - using var _1 = PooledDictionary>.GetInstance(out var updatedSpansByDocumentPath); + using var _1 = PooledDictionary>.GetInstance(out var updatedSpansByDocumentPath); var ordinal = 0; foreach (var debugInfo in debugInfos) @@ -79,10 +79,10 @@ public static ActiveStatementsMap Create( if (!updatedSpansByDocumentPath.TryGetValue(documentName, out var documentInfos)) { - updatedSpansByDocumentPath.Add(documentName, documentInfos = ArrayBuilder<(ManagedActiveStatementDebugInfo, SourceFileSpan, int)>.GetInstance()); + updatedSpansByDocumentPath.Add(documentName, documentInfos = ArrayBuilder<(ManagedActiveStatementDebugInfo, SourceFileSpan, ActiveStatementId)>.GetInstance()); } - documentInfos.Add((debugInfo, new SourceFileSpan(documentName, baseSpan), ordinal++)); + documentInfos.Add((debugInfo, new SourceFileSpan(documentName, baseSpan), new ActiveStatementId(ordinal++))); } foreach (var (_, infos) in updatedSpansByDocumentPath) @@ -93,7 +93,7 @@ public static ActiveStatementsMap Create( var byDocumentPath = updatedSpansByDocumentPath.ToImmutableDictionary( keySelector: entry => entry.Key, elementSelector: entry => entry.Value.SelectAsArray(item => new ActiveStatement( - ordinal: item.ordinal, + id: item.id, flags: item.info.Flags, span: item.span, instructionId: item.info.ActiveInstruction))); @@ -249,7 +249,7 @@ void AddStatement(LinePositionSpan unmappedLineSpan, ActiveStatement activeState Debug.Assert(builder.IsSorted(Comparer.Create((x, y) => x.UnmappedSpan.Start.CompareTo(y.UnmappedSpan.End)))); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private static LinePositionSpan ReverseMapLinePositionSpan(LinePositionSpan unmappedSection, LinePositionSpan mappedSection, LinePositionSpan mappedSpan) diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs index 03cbd749dd828..62ba99219f3af 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs @@ -183,33 +183,31 @@ private PendingUpdate RetrievePendingUpdate() return pendingUpdate; } - private void EndEditSession(out ImmutableArray documentsToReanalyze) + private void EndEditSession() { - documentsToReanalyze = EditSession.GetDocumentsWithReportedDiagnostics(); - var editSessionTelemetryData = EditSession.Telemetry.GetDataAndClear(); _telemetry.LogEditSession(editSessionTelemetryData); } - public void EndSession(out ImmutableArray documentsToReanalyze, out DebuggingSessionTelemetry.Data telemetryData) + public void EndSession(out DebuggingSessionTelemetry.Data telemetryData) { ThrowIfDisposed(); - EndEditSession(out documentsToReanalyze); + EndEditSession(); telemetryData = _telemetry.GetDataAndClear(); _reportTelemetry(telemetryData); Dispose(); } - public void BreakStateOrCapabilitiesChanged(bool? inBreakState, out ImmutableArray documentsToReanalyze) - => RestartEditSession(nonRemappableRegions: null, inBreakState, out documentsToReanalyze); + public void BreakStateOrCapabilitiesChanged(bool? inBreakState) + => RestartEditSession(nonRemappableRegions: null, inBreakState); - internal void RestartEditSession(ImmutableDictionary>? nonRemappableRegions, bool? inBreakState, out ImmutableArray documentsToReanalyze) + internal void RestartEditSession(ImmutableDictionary>? nonRemappableRegions, bool? inBreakState) { ThrowIfDisposed(); - EndEditSession(out documentsToReanalyze); + EndEditSession(); EditSession = new EditSession( this, @@ -435,7 +433,7 @@ private static ImmutableDictionary> GroupToImmutableDiction foreach (var item in items) { - builder.Add(item.Key, item.ToImmutableArray()); + builder.Add(item.Key, [.. item]); } return builder.ToImmutable(); @@ -502,9 +500,6 @@ CommittedSolution.DocumentState.Indeterminate or EditSession.Telemetry.LogRudeEditDiagnostics(analysis.RudeEditErrors, project.State.Attributes.TelemetryId); - // track the document, so that we can refresh or clean diagnostics at the end of edit session: - EditSession.TrackDocumentWithReportedDiagnostics(document.Id); - var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); return analysis.RudeEditErrors.SelectAsArray((e, t) => e.ToDiagnostic(t), tree); } @@ -548,7 +543,7 @@ public async ValueTask EmitSolutionUpdateAsync( }; } - public void CommitSolutionUpdate(out ImmutableArray documentsToReanalyze) + public void CommitSolutionUpdate() { ThrowIfDisposed(); @@ -583,7 +578,7 @@ from region in moduleRegions.Regions _editSessionTelemetry.LogCommitted(); // Restart edit session with no active statements (switching to run mode). - RestartEditSession(newNonRemappableRegions, inBreakState: false, out documentsToReanalyze); + RestartEditSession(newNonRemappableRegions, inBreakState: false); } public void DiscardSolutionUpdate() @@ -592,6 +587,10 @@ public void DiscardSolutionUpdate() _ = RetrievePendingUpdate(); } + /// + /// Returns s for each document of , + /// or default if not in a break state. + /// public async ValueTask>> GetBaseActiveStatementSpansAsync(Solution solution, ImmutableArray documentIds, CancellationToken cancellationToken) { try @@ -725,7 +724,7 @@ public async ValueTask>> GetB unmappedDocumentId = null; } - return new ActiveStatementSpan(activeStatement.Ordinal, span, activeStatement.Flags, unmappedDocumentId); + return new ActiveStatementSpan(activeStatement.Id, span, activeStatement.Flags, unmappedDocumentId); }); } } @@ -734,7 +733,8 @@ public async ValueTask>> GetB documentIndicesByMappedPath.FreeValues(); activeStatementsInChangedDocuments.FreeValues(); - return spans.ToImmutable(); + Debug.Assert(spans.Count == documentIds.Length); + return spans.ToImmutableAndClear(); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -802,16 +802,16 @@ public async ValueTask> GetAdjustedActiveSta { foreach (var activeStatement in analysis.ActiveStatements) { - var i = adjustedMappedSpans.FindIndex((s, ordinal) => s.Ordinal == ordinal, activeStatement.Ordinal); + var i = adjustedMappedSpans.FindIndex(static (s, id) => s.Id == id, activeStatement.Id); if (i >= 0) { - adjustedMappedSpans[i] = new ActiveStatementSpan(activeStatement.Ordinal, activeStatement.Span, activeStatement.Flags, unmappedDocumentId); + adjustedMappedSpans[i] = new ActiveStatementSpan(activeStatement.Id, activeStatement.Span, activeStatement.Flags, unmappedDocumentId); } } } } - return adjustedMappedSpans.ToImmutable(); + return adjustedMappedSpans.ToImmutableAndClear(); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -836,7 +836,7 @@ public ImmutableHashSet GetModulesPreparedForUpdate() { lock (_instance._modulesPreparedForUpdateGuard) { - return _instance._modulesPreparedForUpdate.ToImmutableHashSet(); + return [.. _instance._modulesPreparedForUpdate]; } } diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs index 2b3d5186583d6..2a9b478b6a437 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs @@ -15,7 +15,7 @@ internal sealed class DebuggingSessionTelemetry(Guid solutionSessionId) internal readonly struct Data(DebuggingSessionTelemetry telemetry) { public readonly Guid SolutionSessionId = telemetry._solutionSessionId; - public readonly ImmutableArray EditSessionData = telemetry._editSessionData.ToImmutableArray(); + public readonly ImmutableArray EditSessionData = [.. telemetry._editSessionData]; public readonly int EmptyEditSessionCount = telemetry._emptyEditSessionCount; public readonly int EmptyHotReloadEditSessionCount = telemetry._emptyHotReloadEditSessionCount; } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs index 69800c49f88d5..36607b513ff06 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs @@ -65,6 +65,11 @@ internal enum EditAndContinueCapabilities /// Adding a static or instance field to an existing generic type. /// GenericAddFieldToExistingType = 1 << 9, + + /// + /// The runtime supports adding to InterfaceImpl table. + /// + AddExplicitInterfaceImplementation = 1 << 10, } internal static class EditAndContinueCapabilitiesParser @@ -87,6 +92,7 @@ public static EditAndContinueCapabilities Parse(ImmutableArray capabilit nameof(EditAndContinueCapabilities.GenericAddMethodToExistingType) => EditAndContinueCapabilities.GenericAddMethodToExistingType, nameof(EditAndContinueCapabilities.GenericUpdateMethod) => EditAndContinueCapabilities.GenericUpdateMethod, nameof(EditAndContinueCapabilities.GenericAddFieldToExistingType) => EditAndContinueCapabilities.GenericAddFieldToExistingType, + nameof(EditAndContinueCapabilities.AddExplicitInterfaceImplementation) => EditAndContinueCapabilities.AddExplicitInterfaceImplementation, // To make it eaiser for runtimes to specify more broad capabilities "AddDefinitionToExistingType" => EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddStaticFieldToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType, @@ -123,6 +129,9 @@ public static ImmutableArray ToStringArray(this EditAndContinueCapabilit if (capabilities.HasFlag(EditAndContinueCapabilities.UpdateParameters)) builder.Add(nameof(EditAndContinueCapabilities.UpdateParameters)); - return builder.ToImmutable(); + if (capabilities.HasFlag(EditAndContinueCapabilities.AddExplicitInterfaceImplementation)) + builder.Add(nameof(EditAndContinueCapabilities.AddExplicitInterfaceImplementation)); + + return builder.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilitiesGrantor.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilitiesGrantor.cs index e5eda77eb92a3..71dc24654c92c 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilitiesGrantor.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilitiesGrantor.cs @@ -18,4 +18,19 @@ public bool Grant(EditAndContinueCapabilities capabilities) GrantedCapabilities |= capabilities; return (_availableCapabilities & capabilities) == capabilities; } + + public bool GrantNewTypeDefinition(INamedTypeSymbol type) + { + if (!Grant(EditAndContinueCapabilities.NewTypeDefinition)) + { + return false; + } + + if (type.HasExplicitlyImplementedInterfaceMember() && !Grant(EditAndContinueCapabilities.AddExplicitInterfaceImplementation)) + { + return false; + } + + return true; + } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs deleted file mode 100644 index 12ef5a0b226ae..0000000000000 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Shared.Collections; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.EditAndContinue; - -[Export(typeof(EditAndContinueDiagnosticUpdateSource))] -[Shared] -internal sealed class EditAndContinueDiagnosticUpdateSource : IDiagnosticUpdateSource -{ - private int _diagnosticsVersion; - private bool _previouslyHadDiagnostics; - - /// - /// Represents an increasing integer version of diagnostics from Edit and Continue, which increments - /// when diagnostics might have changed even if there is no associated document changes (eg a restart - /// of an app during Hot Reload) - /// - public int Version => _diagnosticsVersion; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public EditAndContinueDiagnosticUpdateSource(IDiagnosticUpdateSourceRegistrationService registrationService) - => registrationService.Register(this); - - // for testing - [SuppressMessage("RoslynDiagnosticsReliability", "RS0034:Exported parts should have [ImportingConstructor]", Justification = "Used incorrectly by tests")] - internal EditAndContinueDiagnosticUpdateSource() - { - } - - public event EventHandler>? DiagnosticsUpdated; - public event EventHandler? DiagnosticsCleared; - - /// - /// Clears all diagnostics reported thru this source. - /// We do not track the particular reported diagnostics here since we can just clear all of them at once. - /// - public void ClearDiagnostics(bool isSessionEnding = false) - { - // If ClearDiagnostics is called and there weren't any diagnostics previously, then there is no point incrementing - // our version number and potentially invalidating caches unnecessarily. - // If the debug session is ending, however, we want to always increment otherwise we can get stuck. eg if the user - // makes a rude edit during a debug session, but doesn't apply the changes, the rude edit will be raised without - // this class knowing about it, and then if the debug session is stopped, we have no knowledge of any diagnostics here - // so don't bump our version number, but the document checksum also doesn't change, so we get stuck with the rude edit. - if (isSessionEnding || _previouslyHadDiagnostics) - { - _previouslyHadDiagnostics = false; - _diagnosticsVersion++; - } - - DiagnosticsCleared?.Invoke(this, EventArgs.Empty); - } - - /// - /// Reports given set of project or solution level diagnostics. - /// - public void ReportDiagnostics(Workspace workspace, Solution solution, ImmutableArray diagnostics, ImmutableArray<(DocumentId, ImmutableArray Diagnostics)> rudeEdits) - { - RoslynDebug.Assert(solution != null); - - // Even though we only report diagnostics, and not rude edits, we still need to - // ensure that the presence of rude edits are considered when we decide to update - // our version number. - // The array inside rudeEdits won't ever be empty for a given document so we can just - // check the outer array. - if (diagnostics.Any() || rudeEdits.Any()) - { - _previouslyHadDiagnostics = true; - _diagnosticsVersion++; - } - - var updateEvent = DiagnosticsUpdated; - if (updateEvent == null) - { - return; - } - - var documentDiagnostics = diagnostics.WhereAsArray(d => d.DocumentId != null); - var projectDiagnostics = diagnostics.WhereAsArray(d => d.DocumentId == null && d.ProjectId != null); - var solutionDiagnostics = diagnostics.WhereAsArray(d => d.DocumentId == null && d.ProjectId == null); - - using var argsBuilder = TemporaryArray.Empty; - - if (documentDiagnostics.Length > 0) - { - foreach (var (documentId, diagnosticData) in documentDiagnostics.GroupBy(static data => data.DocumentId!)) - { - var diagnosticGroupId = (this, documentId); - - argsBuilder.Add(DiagnosticsUpdatedArgs.DiagnosticsCreated( - diagnosticGroupId, - workspace, - solution, - documentId.ProjectId, - documentId: documentId, - diagnostics: diagnosticData.ToImmutableArray())); - } - } - - if (projectDiagnostics.Length > 0) - { - foreach (var (projectId, diagnosticData) in projectDiagnostics.GroupBy(static data => data.ProjectId!)) - { - var diagnosticGroupId = (this, projectId); - - argsBuilder.Add(DiagnosticsUpdatedArgs.DiagnosticsCreated( - diagnosticGroupId, - workspace, - solution, - projectId, - documentId: null, - diagnostics: diagnosticData.ToImmutableArray())); - } - } - - if (solutionDiagnostics.Length > 0) - { - var diagnosticGroupId = this; - - argsBuilder.Add(DiagnosticsUpdatedArgs.DiagnosticsCreated( - diagnosticGroupId, - workspace, - solution, - projectId: null, - documentId: null, - diagnostics: solutionDiagnostics)); - } - - if (argsBuilder.Count > 0) - { - updateEvent(this, argsBuilder.ToImmutableAndClear()); - } - } -} diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs index fbe2a833404d6..2812e10592465 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs @@ -25,7 +25,7 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; internal sealed class EditAndContinueDocumentAnalysesCache(AsyncLazy baseActiveStatements, AsyncLazy capabilities) { private readonly object _guard = new(); - private readonly Dictionary results, Project baseProject, Document document, ImmutableArray activeStatementSpans)> _analyses = []; + private readonly Dictionary results, Project baseProject, Document document, ImmutableArray activeStatementSpans)> _analyses = []; private readonly AsyncLazy _baseActiveStatements = baseActiveStatements; private readonly AsyncLazy _capabilities = capabilities; @@ -45,7 +45,7 @@ public async ValueTask> GetDocumentAnaly var tasks = documents.Select(document => Task.Run(() => GetDocumentAnalysisAsync(oldSolution, document.oldDocument, document.newDocument, activeStatementSpanProvider, cancellationToken).AsTask(), cancellationToken)); var allResults = await Task.WhenAll(tasks).ConfigureAwait(false); - return allResults.ToImmutableArray(); + return [.. allResults]; } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -97,7 +97,7 @@ public async ValueTask GetDocumentAnalysisAsync( /// /// Calculates unmapped active statement spans in the from spans provided by . /// - private async Task> GetLatestUnmappedActiveStatementSpansAsync(Document? oldDocument, Document newDocument, ActiveStatementSpanProvider newActiveStatementSpanProvider, CancellationToken cancellationToken) + private async Task> GetLatestUnmappedActiveStatementSpansAsync(Document? oldDocument, Document newDocument, ActiveStatementSpanProvider newActiveStatementSpanProvider, CancellationToken cancellationToken) { if (oldDocument == null) { @@ -118,7 +118,7 @@ private async Task> GetLatestUnmappedActiveStat if (!newLineMappings.Any()) { var newMappedDocumentSpans = await newActiveStatementSpanProvider(newDocument.Id, newDocument.FilePath, cancellationToken).ConfigureAwait(false); - return newMappedDocumentSpans.SelectAsArray(s => s.LineSpan); + return newMappedDocumentSpans.SelectAsArray(static s => new ActiveStatementLineSpan(s.Id, s.LineSpan)); } // The document has #line directives. In order to determine all active statement spans in the document @@ -126,7 +126,7 @@ private async Task> GetLatestUnmappedActiveStat // We retrieve the tracking spans for all such documents and then map them back to this document. using var _1 = PooledDictionary>.GetInstance(out var mappedSpansByDocumentPath); - using var _2 = ArrayBuilder.GetInstance(out var activeStatementSpansBuilder); + using var _2 = ArrayBuilder.GetInstance(out var activeStatementSpansBuilder); var baseActiveStatements = await _baseActiveStatements.GetValueAsync(cancellationToken).ConfigureAwait(false); var analyzer = newDocument.Project.Services.GetRequiredService(); @@ -148,20 +148,20 @@ private async Task> GetLatestUnmappedActiveStat } // all baseline spans are being tracked in their corresponding mapped documents (if a span is deleted it's still tracked as empty): - var newMappedDocumentActiveSpan = newMappedDocumentSpans.GetStatement(oldActiveStatement.Statement.Ordinal); + var newMappedDocumentActiveSpan = newMappedDocumentSpans.Single(static (s, id) => s.Id == id, oldActiveStatement.Statement.Id); Debug.Assert(newMappedDocumentActiveSpan.UnmappedDocumentId == null || newMappedDocumentActiveSpan.UnmappedDocumentId == newDocument.Id); // TODO: optimize var newLineMappingContainingActiveSpan = newLineMappings.FirstOrDefault(mapping => mapping.MappedSpan.Span.Contains(newMappedDocumentActiveSpan.LineSpan)); var unmappedSpan = newLineMappingContainingActiveSpan.MappedSpan.IsValid ? newLineMappingContainingActiveSpan.Span : default; - activeStatementSpansBuilder.Add(unmappedSpan); + activeStatementSpansBuilder.Add(new ActiveStatementLineSpan(newMappedDocumentActiveSpan.Id, unmappedSpan)); } - return activeStatementSpansBuilder.ToImmutable(); + return activeStatementSpansBuilder.ToImmutableAndClear(); } - private AsyncLazy GetDocumentAnalysisNoLock(Project baseProject, Document document, ImmutableArray activeStatementSpans) + private AsyncLazy GetDocumentAnalysisNoLock(Project baseProject, Document document, ImmutableArray activeStatementSpans) { // Do not reuse an analysis of the document unless its snasphot is exactly the same as was used to calculate the results. // Note that comparing document snapshots in effect compares the entire solution snapshots (when another document is changed a new solution snapshot is created diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs index 1d69bf53c5dd6..c62f2c83f7ef4 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs @@ -236,7 +236,7 @@ internal static bool TryGetDocumentChecksum(ISymUnmanagedReader5 symReader, stri } algorithmId = symDocument.GetHashAlgorithm(); - checksum = symDocument.GetChecksum().ToImmutableArray(); + checksum = [.. symDocument.GetChecksum()]; return true; } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs index 4c3265462f7e7..010063ad16ddb 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs @@ -10,13 +10,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Contracts.EditAndContinue; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Roslyn.Utilities; -using Microsoft.CodeAnalysis.ErrorReporting; namespace Microsoft.CodeAnalysis.EditAndContinue; @@ -29,9 +29,20 @@ internal sealed class EditAndContinueService : IEditAndContinueService [ExportWorkspaceService(typeof(IEditAndContinueWorkspaceService)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal sealed class WorkspaceService(IEditAndContinueService service) : IEditAndContinueWorkspaceService + internal sealed class WorkspaceService( + IEditAndContinueService service, + [Import(AllowDefault = true)] IEditAndContinueSessionTracker? sessionTracker = null) : IEditAndContinueWorkspaceService { public IEditAndContinueService Service { get; } = service; + public IEditAndContinueSessionTracker SessionTracker { get; } = sessionTracker ?? VoidSessionTracker.Instance; + } + + private sealed class VoidSessionTracker : IEditAndContinueSessionTracker + { + public static readonly VoidSessionTracker Instance = new(); + + public bool IsSessionActive => false; + public ImmutableArray ApplyChangesDiagnostics => []; } internal static readonly TraceLog Log; @@ -109,7 +120,7 @@ private ImmutableArray GetActiveDebuggingSessions() { lock (_debuggingSessions) { - return _debuggingSessions.ToImmutableArray(); + return [.. _debuggingSessions]; } } @@ -146,7 +157,7 @@ public async ValueTask StartDebuggingSessionAsync( } else { - initialDocumentStates = SpecializedCollections.EmptyEnumerable>(); + initialDocumentStates = []; } var sessionId = new DebuggingSessionId(Interlocked.Increment(ref s_debuggingSessionId)); @@ -174,7 +185,7 @@ group documentId by documentId.ProjectId into projectDocumentIds let project = solution.GetRequiredProject(projectDocumentIds.Key) select (project, from documentId in projectDocumentIds select project.State.DocumentStates.GetState(documentId)); - public void EndDebuggingSession(DebuggingSessionId sessionId, out ImmutableArray documentsToReanalyze) + public void EndDebuggingSession(DebuggingSessionId sessionId) { DebuggingSession? debuggingSession; lock (_debuggingSessions) @@ -184,16 +195,16 @@ public void EndDebuggingSession(DebuggingSessionId sessionId, out ImmutableArray Contract.ThrowIfNull(debuggingSession, "Debugging session has not started."); - debuggingSession.EndSession(out documentsToReanalyze, out var telemetryData); + debuggingSession.EndSession(out var telemetryData); Log.Write("Session #{0} ended.", debuggingSession.Id.Ordinal); } - public void BreakStateOrCapabilitiesChanged(DebuggingSessionId sessionId, bool? inBreakState, out ImmutableArray documentsToReanalyze) + public void BreakStateOrCapabilitiesChanged(DebuggingSessionId sessionId, bool? inBreakState) { var debuggingSession = TryGetDebuggingSession(sessionId); Contract.ThrowIfNull(debuggingSession); - debuggingSession.BreakStateOrCapabilitiesChanged(inBreakState, out documentsToReanalyze); + debuggingSession.BreakStateOrCapabilitiesChanged(inBreakState); } public ValueTask> GetDocumentDiagnosticsAsync(Document document, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) @@ -219,12 +230,12 @@ public ValueTask EmitSolutionUpdateAsync( return debuggingSession.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, cancellationToken); } - public void CommitSolutionUpdate(DebuggingSessionId sessionId, out ImmutableArray documentsToReanalyze) + public void CommitSolutionUpdate(DebuggingSessionId sessionId) { var debuggingSession = TryGetDebuggingSession(sessionId); Contract.ThrowIfNull(debuggingSession); - debuggingSession.CommitSolutionUpdate(out documentsToReanalyze); + debuggingSession.CommitSolutionUpdate(); } public void DiscardSolutionUpdate(DebuggingSessionId sessionId) diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index fc558099aebaf..51057d7b7bcef 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -85,14 +85,6 @@ internal sealed class EditSession /// internal readonly bool InBreakState; - /// - /// A is added whenever EnC analyzer reports - /// rude edits or module diagnostics. At the end of the session we ask the diagnostic analyzer to reanalyze - /// the documents to clean up the diagnostics. - /// - private readonly HashSet _documentsWithReportedDiagnostics = []; - private readonly object _documentsWithReportedDiagnosticsGuard = new(); - internal EditSession( DebuggingSession debuggingSession, ImmutableDictionary> nonRemappableRegions, @@ -181,7 +173,7 @@ internal EditSession( diagnostics.Add(Diagnostic.Create(descriptor, location, messageArgs)); } - return diagnostics.ToImmutable(); + return diagnostics.ToImmutableAndClear(); } private static async IAsyncEnumerable CreateChangedLocationsAsync(Project oldProject, Project newProject, ImmutableArray documentAnalyses, [EnumeratorCancellation] CancellationToken cancellationToken) @@ -558,22 +550,6 @@ internal static async IAsyncEnumerable GetChangedDocumentsAsync(Proj return (analyses, documentDiagnostics.ToImmutable()); } - internal ImmutableArray GetDocumentsWithReportedDiagnostics() - { - lock (_documentsWithReportedDiagnosticsGuard) - { - return ImmutableArray.CreateRange(_documentsWithReportedDiagnostics); - } - } - - internal void TrackDocumentWithReportedDiagnostics(DocumentId documentId) - { - lock (_documentsWithReportedDiagnosticsGuard) - { - _documentsWithReportedDiagnostics.Add(documentId); - } - } - private static ProjectAnalysisSummary GetProjectAnalysisSummary(ImmutableArray documentAnalyses) { var hasChanges = false; @@ -750,7 +726,7 @@ internal static void MergePartialEdits( if (edits.Count == mergedEditsBuilder.Count) { mergedEdits = mergedEditsBuilder.ToImmutable(); - addedSymbols = addedSymbolsBuilder.ToImmutableHashSet(); + addedSymbols = [.. addedSymbolsBuilder]; return; } @@ -804,7 +780,7 @@ internal static void MergePartialEdits( } mergedEdits = mergedEditsBuilder.ToImmutable(); - addedSymbols = addedSymbolsBuilder.ToImmutableHashSet(); + addedSymbols = [.. addedSymbolsBuilder]; } public async ValueTask EmitSolutionUpdateAsync(Solution solution, ActiveStatementSpanProvider solutionActiveStatementSpanProvider, UpdateId updateId, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs b/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs index d27a67d59ed99..730e2abe05c3d 100644 --- a/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs +++ b/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs @@ -90,7 +90,7 @@ public async Task> GetAllDiagnosticsAsync(Solution so diagnostics.AddRange(projectEmitDiagnostics); } - return diagnostics.ToImmutable(); + return diagnostics.ToImmutableAndClear(); } internal static async ValueTask> GetHotReloadDiagnosticsAsync( @@ -164,6 +164,6 @@ internal static async ValueTask> GetH } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/EditAndContinue/IActiveStatementSpanProvider.cs b/src/Features/Core/Portable/EditAndContinue/IActiveStatementSpanFactory.cs similarity index 97% rename from src/Features/Core/Portable/EditAndContinue/IActiveStatementSpanProvider.cs rename to src/Features/Core/Portable/EditAndContinue/IActiveStatementSpanFactory.cs index 19de6d28cc9ef..694b88f5d48c9 100644 --- a/src/Features/Core/Portable/EditAndContinue/IActiveStatementSpanProvider.cs +++ b/src/Features/Core/Portable/EditAndContinue/IActiveStatementSpanFactory.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; -internal interface IActiveStatementSpanProvider +internal interface IActiveStatementSpanFactory { /// /// Returns base mapped active statement spans contained in each specified document projected to a given solution snapshot diff --git a/src/Features/Core/Portable/EditAndContinue/IActiveStatementSpanLocator.cs b/src/Features/Core/Portable/EditAndContinue/IActiveStatementSpanLocator.cs new file mode 100644 index 0000000000000..a780293e22248 --- /dev/null +++ b/src/Features/Core/Portable/EditAndContinue/IActiveStatementSpanLocator.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.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.EditAndContinue; + +internal interface IActiveStatementSpanLocator : IWorkspaceService +{ + /// + /// Returns current locations of the active statement tracking spans in the specified document snapshot (#line target document). + /// + /// Empty array if tracking spans are not available for the document. + ValueTask> GetSpansAsync(Solution solution, DocumentId? documentId, string filePath, CancellationToken cancellationToken); +} diff --git a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueAnalyzer.cs index 03a68cfd19c34..5e6c4c41282b4 100644 --- a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueAnalyzer.cs @@ -17,7 +17,7 @@ Task AnalyzeDocumentAsync( Project baseProject, AsyncLazy lazyBaseActiveStatements, Document document, - ImmutableArray newActiveStatementSpans, + ImmutableArray newActiveStatementSpans, AsyncLazy lazyCapabilities, CancellationToken cancellationToken); diff --git a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs index 322a5f20f6634..32c1360a2d976 100644 --- a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs @@ -5,15 +5,15 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; +using Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis.EditAndContinue; internal interface IEditAndContinueWorkspaceService : IWorkspaceService { IEditAndContinueService Service { get; } + IEditAndContinueSessionTracker SessionTracker { get; } } internal interface IEditAndContinueService @@ -21,12 +21,12 @@ internal interface IEditAndContinueService ValueTask> GetDocumentDiagnosticsAsync(Document document, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken); ValueTask EmitSolutionUpdateAsync(DebuggingSessionId sessionId, Solution solution, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken); - void CommitSolutionUpdate(DebuggingSessionId sessionId, out ImmutableArray documentsToReanalyze); + void CommitSolutionUpdate(DebuggingSessionId sessionId); void DiscardSolutionUpdate(DebuggingSessionId sessionId); ValueTask StartDebuggingSessionAsync(Solution solution, IManagedHotReloadService debuggerService, IPdbMatchingSourceTextProvider sourceTextProvider, ImmutableArray captureMatchingDocuments, bool captureAllMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken); - void BreakStateOrCapabilitiesChanged(DebuggingSessionId sessionId, bool? inBreakState, out ImmutableArray documentsToReanalyze); - void EndDebuggingSession(DebuggingSessionId sessionId, out ImmutableArray documentsToReanalyze); + void BreakStateOrCapabilitiesChanged(DebuggingSessionId sessionId, bool? inBreakState); + void EndDebuggingSession(DebuggingSessionId sessionId); ValueTask>> GetBaseActiveStatementSpansAsync(DebuggingSessionId sessionId, Solution solution, ImmutableArray documentIds, CancellationToken cancellationToken); ValueTask> GetAdjustedActiveStatementSpansAsync(DebuggingSessionId sessionId, TextDocument document, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken); diff --git a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueSessionTracker.cs b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueSessionTracker.cs new file mode 100644 index 0000000000000..03039ed628eb9 --- /dev/null +++ b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueSessionTracker.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.EditAndContinue; + +/// +/// Exposes EnC and Hot Reload session state to in-proc components. +/// +internal interface IEditAndContinueSessionTracker +{ + /// + /// True while Hot Reload or EnC session is active. + /// + bool IsSessionActive { get; } + + /// + /// Diagnostics reported by the last call. + /// Includes emit errors and issues reported by the debugger when applying changes. + /// Does not include rude edits, which are reported by . + /// + ImmutableArray ApplyChangesDiagnostics { get; } +} diff --git a/src/Features/Core/Portable/EditAndContinue/MemberBody.cs b/src/Features/Core/Portable/EditAndContinue/MemberBody.cs index 651177ca09e6b..168225b359a4a 100644 --- a/src/Features/Core/Portable/EditAndContinue/MemberBody.cs +++ b/src/Features/Core/Portable/EditAndContinue/MemberBody.cs @@ -42,7 +42,7 @@ public virtual bool IsExcludedActiveStatementSpanWithinEnvelope(TextSpan span) public SyntaxNode FindStatement(TextSpan span, out int statementPart) => FindStatementAndPartner(span, partnerDeclarationBody: null, out _, out statementPart); - public IEnumerable GetOverlappingActiveStatements(ImmutableArray statements) + public IEnumerable GetOverlappingActiveStatementIndices(ImmutableArray statements) { var envelope = Envelope; diff --git a/src/Features/Core/Portable/EditAndContinue/ProjectDiagnostics.cs b/src/Features/Core/Portable/EditAndContinue/ProjectDiagnostics.cs index 52218cb41f9ad..67ab5b683bf09 100644 --- a/src/Features/Core/Portable/EditAndContinue/ProjectDiagnostics.cs +++ b/src/Features/Core/Portable/EditAndContinue/ProjectDiagnostics.cs @@ -29,6 +29,6 @@ public static ImmutableArray ToDiagnosticData(this ImmutableArra } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs index 25372a0ed071c..be246e9a748e4 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs @@ -32,7 +32,7 @@ internal interface ICallback /// /// Returns ids of documents for which diagnostics need to be refreshed in-proc. /// - ValueTask> CommitSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken); + ValueTask CommitSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken); ValueTask DiscardSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken); ValueTask StartDebuggingSessionAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, ImmutableArray captureMatchingDocuments, bool captureAllMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken); @@ -40,12 +40,12 @@ internal interface ICallback /// /// Returns ids of documents for which diagnostics need to be refreshed in-proc. /// - ValueTask> BreakStateOrCapabilitiesChangedAsync(DebuggingSessionId sessionId, bool? isBreakState, CancellationToken cancellationToken); + ValueTask BreakStateOrCapabilitiesChangedAsync(DebuggingSessionId sessionId, bool? isBreakState, CancellationToken cancellationToken); /// /// Returns ids of documents for which diagnostics need to be refreshed in-proc. /// - ValueTask> EndDebuggingSessionAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken); + ValueTask EndDebuggingSessionAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken); ValueTask>> GetBaseActiveStatementSpansAsync(Checksum solutionChecksum, DebuggingSessionId sessionId, ImmutableArray documentIds, CancellationToken cancellationToken); ValueTask> GetAdjustedActiveStatementSpansAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DebuggingSessionId sessionId, DocumentId documentId, CancellationToken cancellationToken); diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs index d2b0067d344fb..0421adfb42391 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs @@ -4,85 +4,52 @@ using System; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.EditAndContinue; -internal sealed class RemoteDebuggingSessionProxy(Workspace workspace, IDisposable? connection, DebuggingSessionId sessionId) : IActiveStatementSpanProvider, IDisposable +internal sealed class RemoteDebuggingSessionProxy(SolutionServices services, IDisposable? connection, DebuggingSessionId sessionId) : IActiveStatementSpanFactory, IDisposable { - private readonly IDisposable? _connection = connection; - private readonly DebuggingSessionId _sessionId = sessionId; - private readonly Workspace _workspace = workspace; - public void Dispose() - { - _connection?.Dispose(); - } + => connection?.Dispose(); private IEditAndContinueService GetLocalService() - => _workspace.Services.GetRequiredService().Service; + => services.GetRequiredService().Service; - public async ValueTask BreakStateOrCapabilitiesChangedAsync(IDiagnosticAnalyzerService diagnosticService, EditAndContinueDiagnosticUpdateSource diagnosticUpdateSource, bool? inBreakState, CancellationToken cancellationToken) + public async ValueTask BreakStateOrCapabilitiesChangedAsync(bool? inBreakState, CancellationToken cancellationToken) { - ImmutableArray documentsToReanalyze; - - var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); if (client == null) { - GetLocalService().BreakStateOrCapabilitiesChanged(_sessionId, inBreakState, out documentsToReanalyze); + GetLocalService().BreakStateOrCapabilitiesChanged(sessionId, inBreakState); } else { - var documentsToReanalyzeOpt = await client.TryInvokeAsync>( - (service, cancallationToken) => service.BreakStateOrCapabilitiesChangedAsync(_sessionId, inBreakState, cancellationToken), + await client.TryInvokeAsync( + (service, cancallationToken) => service.BreakStateOrCapabilitiesChangedAsync(sessionId, inBreakState, cancellationToken), cancellationToken).ConfigureAwait(false); - - documentsToReanalyze = documentsToReanalyzeOpt.HasValue ? documentsToReanalyzeOpt.Value : []; } - - // clear all reported rude edits: - diagnosticService.Reanalyze(_workspace, projectIds: null, documentIds: documentsToReanalyze, highPriority: false); - - // clear emit/apply diagnostics reported previously: - diagnosticUpdateSource.ClearDiagnostics(isSessionEnding: false); } - public async ValueTask EndDebuggingSessionAsync(Solution compileTimeSolution, EditAndContinueDiagnosticUpdateSource diagnosticUpdateSource, IDiagnosticAnalyzerService diagnosticService, CancellationToken cancellationToken) + public async ValueTask EndDebuggingSessionAsync(CancellationToken cancellationToken) { - ImmutableArray documentsToReanalyze; - - var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); if (client == null) { - GetLocalService().EndDebuggingSession(_sessionId, out documentsToReanalyze); + GetLocalService().EndDebuggingSession(sessionId); } else { - var documentsToReanalyzeOpt = await client.TryInvokeAsync>( - (service, cancallationToken) => service.EndDebuggingSessionAsync(_sessionId, cancellationToken), + await client.TryInvokeAsync( + (service, cancallationToken) => service.EndDebuggingSessionAsync(sessionId, cancellationToken), cancellationToken).ConfigureAwait(false); - - documentsToReanalyze = documentsToReanalyzeOpt.HasValue ? documentsToReanalyzeOpt.Value : []; } - var designTimeDocumentsToReanalyze = await CompileTimeSolutionProvider.GetDesignTimeDocumentsAsync( - compileTimeSolution, documentsToReanalyze, designTimeSolution: _workspace.CurrentSolution, cancellationToken).ConfigureAwait(false); - - // clear all reported rude edits: - diagnosticService.Reanalyze(_workspace, projectIds: null, documentIds: designTimeDocumentsToReanalyze, highPriority: false); - - // clear emit/apply diagnostics reported previously: - diagnosticUpdateSource.ClearDiagnostics(isSessionEnding: true); - Dispose(); } @@ -93,8 +60,6 @@ public async ValueTask EndDebuggingSessionAsync(Solution compileTimeSolution, Ed DiagnosticData? syntaxError)> EmitSolutionUpdateAsync( Solution solution, ActiveStatementSpanProvider activeStatementSpanProvider, - IDiagnosticAnalyzerService diagnosticService, - EditAndContinueDiagnosticUpdateSource diagnosticUpdateSource, CancellationToken cancellationToken) { ModuleUpdates moduleUpdates; @@ -104,10 +69,10 @@ public async ValueTask EndDebuggingSessionAsync(Solution compileTimeSolution, Ed try { - var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); if (client == null) { - var results = await GetLocalService().EmitSolutionUpdateAsync(_sessionId, solution, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); + var results = await GetLocalService().EmitSolutionUpdateAsync(sessionId, solution, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); moduleUpdates = results.ModuleUpdates; diagnosticData = results.Diagnostics.ToDiagnosticData(solution); rudeEdits = results.RudeEdits; @@ -117,7 +82,7 @@ public async ValueTask EndDebuggingSessionAsync(Solution compileTimeSolution, Ed { var result = await client.TryInvokeAsync( solution, - (service, solutionInfo, callbackId, cancellationToken) => service.EmitSolutionUpdateAsync(solutionInfo, callbackId, _sessionId, cancellationToken), + (service, solutionInfo, callbackId, cancellationToken) => service.EmitSolutionUpdateAsync(solutionInfo, callbackId, sessionId, cancellationToken), callbackTarget: new ActiveStatementSpanProviderCallback(activeStatementSpanProvider), cancellationToken).ConfigureAwait(false); @@ -145,15 +110,6 @@ public async ValueTask EndDebuggingSessionAsync(Solution compileTimeSolution, Ed syntaxError = null; } - // clear emit/apply diagnostics reported previously: - diagnosticUpdateSource.ClearDiagnostics(isSessionEnding: false); - - // clear all reported rude edits: - diagnosticService.Reanalyze(_workspace, projectIds: null, documentIds: rudeEdits.Select(d => d.DocumentId), highPriority: false); - - // report emit/apply diagnostics: - diagnosticUpdateSource.ReportDiagnostics(_workspace, solution, diagnosticData, rudeEdits); - return (moduleUpdates, diagnosticData, rudeEdits, syntaxError); } @@ -169,53 +125,46 @@ private static ImmutableArray GetInternalErrorDiagnosticData(Sol return [DiagnosticData.Create(solution, diagnostic, project: null)]; } - public async ValueTask CommitSolutionUpdateAsync(IDiagnosticAnalyzerService diagnosticService, CancellationToken cancellationToken) + public async ValueTask CommitSolutionUpdateAsync(CancellationToken cancellationToken) { - ImmutableArray documentsToReanalyze; - - var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); if (client == null) { - GetLocalService().CommitSolutionUpdate(_sessionId, out documentsToReanalyze); + GetLocalService().CommitSolutionUpdate(sessionId); } else { - var documentsToReanalyzeOpt = await client.TryInvokeAsync>( - (service, cancallationToken) => service.CommitSolutionUpdateAsync(_sessionId, cancellationToken), + await client.TryInvokeAsync( + (service, cancallationToken) => service.CommitSolutionUpdateAsync(sessionId, cancellationToken), cancellationToken).ConfigureAwait(false); - - documentsToReanalyze = documentsToReanalyzeOpt.HasValue ? documentsToReanalyzeOpt.Value : []; } - - // clear all reported rude edits: - diagnosticService.Reanalyze(_workspace, projectIds: null, documentIds: documentsToReanalyze, highPriority: false); } public async ValueTask DiscardSolutionUpdateAsync(CancellationToken cancellationToken) { - var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); if (client == null) { - GetLocalService().DiscardSolutionUpdate(_sessionId); + GetLocalService().DiscardSolutionUpdate(sessionId); return; } await client.TryInvokeAsync( - (service, cancellationToken) => service.DiscardSolutionUpdateAsync(_sessionId, cancellationToken), + (service, cancellationToken) => service.DiscardSolutionUpdateAsync(sessionId, cancellationToken), cancellationToken).ConfigureAwait(false); } public async ValueTask>> GetBaseActiveStatementSpansAsync(Solution solution, ImmutableArray documentIds, CancellationToken cancellationToken) { - var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); if (client == null) { - return await GetLocalService().GetBaseActiveStatementSpansAsync(_sessionId, solution, documentIds, cancellationToken).ConfigureAwait(false); + return await GetLocalService().GetBaseActiveStatementSpansAsync(sessionId, solution, documentIds, cancellationToken).ConfigureAwait(false); } var result = await client.TryInvokeAsync>>( solution, - (service, solutionInfo, cancellationToken) => service.GetBaseActiveStatementSpansAsync(solutionInfo, _sessionId, documentIds, cancellationToken), + (service, solutionInfo, cancellationToken) => service.GetBaseActiveStatementSpansAsync(solutionInfo, sessionId, documentIds, cancellationToken), cancellationToken).ConfigureAwait(false); return result.HasValue ? result.Value : []; @@ -229,15 +178,15 @@ public async ValueTask> GetAdjustedActiveSta return []; } - var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); if (client == null) { - return await GetLocalService().GetAdjustedActiveStatementSpansAsync(_sessionId, document, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); + return await GetLocalService().GetAdjustedActiveStatementSpansAsync(sessionId, document, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); } var result = await client.TryInvokeAsync>( document.Project.Solution, - (service, solutionInfo, callbackId, cancellationToken) => service.GetAdjustedActiveStatementSpansAsync(solutionInfo, callbackId, _sessionId, document.Id, cancellationToken), + (service, solutionInfo, callbackId, cancellationToken) => service.GetAdjustedActiveStatementSpansAsync(solutionInfo, callbackId, sessionId, document.Id, cancellationToken), callbackTarget: new ActiveStatementSpanProviderCallback(activeStatementSpanProvider), cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs index 9e93e6c963a0f..d65feef975af4 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs @@ -5,15 +5,14 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.EditAndContinue; @@ -23,17 +22,13 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; /// Encapsulates all RPC logic as well as dispatching to the local service if the remote service is disabled. /// THe facade is useful for targeted testing of serialization/deserialization of EnC service calls. /// -internal readonly partial struct RemoteEditAndContinueServiceProxy(Workspace workspace) +internal readonly partial struct RemoteEditAndContinueServiceProxy(SolutionServices services) { [ExportRemoteServiceCallbackDispatcher(typeof(IRemoteEditAndContinueService)), Shared] - internal sealed class CallbackDispatcher : RemoteServiceCallbackDispatcher, IRemoteEditAndContinueService.ICallback + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class CallbackDispatcher() : RemoteServiceCallbackDispatcher, IRemoteEditAndContinueService.ICallback { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CallbackDispatcher() - { - } - public ValueTask> GetSpansAsync(RemoteServiceCallbackId callbackId, DocumentId? documentId, string filePath, CancellationToken cancellationToken) => ((ActiveStatementSpanProviderCallback)GetCallback(callbackId)).GetSpansAsync(documentId, filePath, cancellationToken); @@ -119,10 +114,8 @@ public async ValueTask> GetCapabilitiesAsync(Cancellation } } - public readonly Workspace Workspace = workspace; - private IEditAndContinueService GetLocalService() - => Workspace.Services.GetRequiredService().Service; + => services.GetRequiredService().Service; public async ValueTask StartDebuggingSessionAsync( Solution solution, @@ -133,11 +126,11 @@ private IEditAndContinueService GetLocalService() bool reportDiagnostics, CancellationToken cancellationToken) { - var client = await RemoteHostClient.TryGetClientAsync(Workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); if (client == null) { var sessionId = await GetLocalService().StartDebuggingSessionAsync(solution, debuggerService, sourceTextProvider, captureMatchingDocuments, captureAllMatchingDocuments, reportDiagnostics, cancellationToken).ConfigureAwait(false); - return new RemoteDebuggingSessionProxy(Workspace, LocalConnection.Instance, sessionId); + return new RemoteDebuggingSessionProxy(solution.Services, LocalConnection.Instance, sessionId); } // need to keep the providers alive until the session ends: @@ -151,14 +144,14 @@ private IEditAndContinueService GetLocalService() if (sessionIdOpt.HasValue) { - return new RemoteDebuggingSessionProxy(Workspace, connection, sessionIdOpt.Value); + return new RemoteDebuggingSessionProxy(solution.Services, connection, sessionIdOpt.Value); } connection.Dispose(); return null; } - public async ValueTask> GetDocumentDiagnosticsAsync(Document document, Document designTimeDocument, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) + public async ValueTask> GetDocumentDiagnosticsAsync(Document document, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) { // filter out documents that are not synchronized to remote process before we attempt remote invoke: if (!RemoteSupportedLanguages.IsSupported(document.Project.Language)) @@ -166,18 +159,11 @@ public async ValueTask> GetDocumentDiagnosticsAsync(D return []; } - var client = await RemoteHostClient.TryGetClientAsync(Workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); if (client == null) { var diagnostics = await GetLocalService().GetDocumentDiagnosticsAsync(document, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); - - if (designTimeDocument != document) - { - diagnostics = diagnostics.SelectAsArray( - diagnostic => RemapLocation(designTimeDocument, DiagnosticData.Create(document.Project.Solution, diagnostic, document.Project))); - } - - return diagnostics; + return diagnostics.SelectAsArray(diagnostic => DiagnosticData.Create(document.Project.Solution, diagnostic, document.Project)); } var diagnosticData = await client.TryInvokeAsync>( @@ -186,54 +172,12 @@ public async ValueTask> GetDocumentDiagnosticsAsync(D callbackTarget: new ActiveStatementSpanProviderCallback(activeStatementSpanProvider), cancellationToken).ConfigureAwait(false); - if (!diagnosticData.HasValue) - { - return []; - } - - var project = document.Project; - - using var _ = ArrayBuilder.GetInstance(out var result); - foreach (var data in diagnosticData.Value) - { - Debug.Assert(data.DataLocation != null); - - Diagnostic diagnostic; - - // Workaround for solution crawler not supporting mapped locations to make Razor work. - // We pretend the diagnostic is in the original document, but use the mapped line span. - // Razor will ignore the column (which will be off because #line directives can't currently map columns) and only use the line number. - if (designTimeDocument != document) - { - diagnostic = RemapLocation(designTimeDocument, data); - } - else - { - diagnostic = await data.ToDiagnosticAsync(document.Project, cancellationToken).ConfigureAwait(false); - } - - result.Add(diagnostic); - } - - return result.ToImmutable(); - } - - private static Diagnostic RemapLocation(Document designTimeDocument, DiagnosticData data) - { - Debug.Assert(data.DataLocation != null); - Debug.Assert(designTimeDocument.FilePath != null); - - // If the location in the generated document is in a scope of user-visible #line mapping use the mapped span, - // otherwise (if it's hidden) display the diagnostic at the start of the file. - var span = data.DataLocation.UnmappedFileSpan != data.DataLocation.MappedFileSpan ? data.DataLocation.MappedFileSpan.Span : default; - var location = Location.Create(designTimeDocument.FilePath, textSpan: default, span); - - return data.ToDiagnostic(location, []); + return diagnosticData.HasValue ? diagnosticData.Value : []; } public async ValueTask SetFileLoggingDirectoryAsync(string? logDirectory, CancellationToken cancellationToken) { - var client = await RemoteHostClient.TryGetClientAsync(Workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); if (client == null) { GetLocalService().SetFileLoggingDirectory(logDirectory); diff --git a/src/Features/Core/Portable/EditAndContinue/TraceLog.cs b/src/Features/Core/Portable/EditAndContinue/TraceLog.cs index 8c0afaf35a6fd..a96471c54ad53 100644 --- a/src/Features/Core/Portable/EditAndContinue/TraceLog.cs +++ b/src/Features/Core/Portable/EditAndContinue/TraceLog.cs @@ -159,7 +159,7 @@ public void Write(DebuggingSessionId sessionId, ImmutableArray bytes, stri try { path = Path.Combine(CreateSessionDirectory(sessionId, directory), fileName); - File.WriteAllBytes(path, bytes.ToArray()); + File.WriteAllBytes(path, [.. bytes]); } catch (Exception e) { diff --git a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs index e8f40615816a0..b9d00876835b6 100644 --- a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs +++ b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs @@ -35,45 +35,6 @@ public static LinePositionSpan ToLinePositionSpan(this SourceSpan span) public static SourceSpan ToSourceSpan(this LinePositionSpan span) => new(span.Start.Line, span.Start.Character, span.End.Line, span.End.Character); - public static ActiveStatement GetStatement(this ImmutableArray statements, int ordinal) - { - foreach (var item in statements) - { - if (item.Ordinal == ordinal) - { - return item; - } - } - - throw ExceptionUtilities.UnexpectedValue(ordinal); - } - - public static ActiveStatementSpan GetStatement(this ImmutableArray statements, int ordinal) - { - foreach (var item in statements) - { - if (item.Ordinal == ordinal) - { - return item; - } - } - - throw ExceptionUtilities.UnexpectedValue(ordinal); - } - - public static UnmappedActiveStatement GetStatement(this ImmutableArray statements, int ordinal) - { - foreach (var item in statements) - { - if (item.Statement.Ordinal == ordinal) - { - return item; - } - } - - throw ExceptionUtilities.UnexpectedValue(ordinal); - } - /// /// True if the project supports Edit and Continue. /// Only depends on the language of the project and never changes. @@ -226,4 +187,10 @@ private static bool ParsePrimaryParameterBackingFieldName(string fieldName, [Not public static ISymbol PartialAsImplementation(this ISymbol symbol) => symbol is IMethodSymbol { PartialImplementationPart: { } impl } ? impl : symbol; + + /// + /// Returns true if any member of the type implements an interface member explicitly. + /// + public static bool HasExplicitlyImplementedInterfaceMember(this INamedTypeSymbol type) + => type.GetMembers().Any(static member => member.ExplicitInterfaceImplementations().Any()); } diff --git a/src/Features/Core/Portable/EmbeddedLanguages/Classification/AbstractEmbeddedLanguageClassificationService.cs b/src/Features/Core/Portable/EmbeddedLanguages/Classification/AbstractEmbeddedLanguageClassificationService.cs index 2aa588647f843..6b73e38851462 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/Classification/AbstractEmbeddedLanguageClassificationService.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/Classification/AbstractEmbeddedLanguageClassificationService.cs @@ -86,10 +86,9 @@ public void VisitTokens(SyntaxNode node) using var pooledStack = SharedPools.Default>().GetPooledObject(); var stack = pooledStack.Object; stack.Push(node); - while (stack.Count > 0) + while (stack.TryPop(out var currentNodeOrToken)) { _cancellationToken.ThrowIfCancellationRequested(); - var currentNodeOrToken = stack.Pop(); if (currentNodeOrToken.Span.IntersectsWith(_textSpan)) { if (currentNodeOrToken.IsNode) diff --git a/src/Features/Core/Portable/EmbeddedLanguages/Json/JsonLexer.cs b/src/Features/Core/Portable/EmbeddedLanguages/Json/JsonLexer.cs index 8476e7be98fc0..bc07d43a097fd 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/Json/JsonLexer.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/Json/JsonLexer.cs @@ -230,7 +230,7 @@ private ImmutableArray ScanTrivia(bool leading) break; } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private JsonTrivia? ScanEndOfLine() diff --git a/src/Features/Core/Portable/EmbeddedLanguages/Json/JsonParser.cs b/src/Features/Core/Portable/EmbeddedLanguages/Json/JsonParser.cs index 018d43fea8a0b..75ce0a69046bb 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/Json/JsonParser.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/Json/JsonParser.cs @@ -322,7 +322,7 @@ private ImmutableArray ParseSequenceWorker() while (ShouldConsumeSequenceElement()) result.Add(ParseValue()); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private JsonSeparatedList ParseCommaSeparatedSequence() diff --git a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/AbstractRegexDiagnosticAnalyzer.cs b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/AbstractRegexDiagnosticAnalyzer.cs index 3374e1e8bfaa7..10907db3d5b17 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/AbstractRegexDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/AbstractRegexDiagnosticAnalyzer.cs @@ -9,6 +9,8 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.EmbeddedLanguages; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Features.EmbeddedLanguages.RegularExpressions.LanguageServices; @@ -50,13 +52,12 @@ public void Analyze(SemanticModelAnalysisContext context) // Use an actual stack object so that we don't blow the actual stack through recursion. var root = context.GetAnalysisRoot(findInTrivia: true); - var stack = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(root); - while (stack.Count != 0) + while (stack.TryPop(out var current)) { cancellationToken.ThrowIfCancellationRequested(); - var current = stack.Pop(); foreach (var child in current.ChildNodesAndTokens()) { diff --git a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/RegexBraceMatcher.cs b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/RegexBraceMatcher.cs index c241855ab29a3..f0bfcdcaea0bd 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/RegexBraceMatcher.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/RegexBraceMatcher.cs @@ -80,7 +80,7 @@ public RegexBraceMatcher() return null; var firstChar = trivia.Value.VirtualChars[0]; - var lastChar = trivia.Value.VirtualChars[trivia.Value.VirtualChars.Length - 1]; + var lastChar = trivia.Value.VirtualChars[^1]; return firstChar != '(' || lastChar != ')' ? null : new BraceMatchingResult(firstChar.Span, lastChar.Span); diff --git a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/RegexEmbeddedCompletionProvider.cs b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/RegexEmbeddedCompletionProvider.cs index cd1b098a8c987..f08cd73226f42 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/RegexEmbeddedCompletionProvider.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/RegexEmbeddedCompletionProvider.cs @@ -97,11 +97,11 @@ not CompletionTriggerKind.InvokeAndCommitIfUnique and var change = embeddedItem.Change; var textChange = change.TextChange; - properties.Add(new KeyValuePair(StartKey, textChange.Span.Start.ToString())); - properties.Add(new KeyValuePair(LengthKey, textChange.Span.Length.ToString())); - properties.Add(new KeyValuePair(NewTextKey, textChange.NewText)); - properties.Add(new KeyValuePair(DescriptionKey, embeddedItem.FullDescription)); - properties.Add(new KeyValuePair(AbstractAggregateEmbeddedLanguageCompletionProvider.EmbeddedProviderName, Name)); + properties.Add(KeyValuePairUtil.Create(StartKey, textChange.Span.Start.ToString())); + properties.Add(KeyValuePairUtil.Create(LengthKey, textChange.Span.Length.ToString())); + properties.Add(KeyValuePairUtil.Create(NewTextKey, textChange.NewText)); + properties.Add(KeyValuePairUtil.Create(DescriptionKey, embeddedItem.FullDescription)); + properties.Add(KeyValuePairUtil.Create(AbstractAggregateEmbeddedLanguageCompletionProvider.EmbeddedProviderName, Name)); if (change.NewPosition != null) { diff --git a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/RegexLexer.cs b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/RegexLexer.cs index e11ef92f6a82b..397fa4abc0efc 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/RegexLexer.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/RegexLexer.cs @@ -124,7 +124,7 @@ private ImmutableArray ScanLeadingTrivia(bool allowTrivia, RegexOpt break; } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public RegexTrivia? ScanComment(RegexOptions options) diff --git a/src/Features/Core/Portable/EmbeddedLanguages/StackFrame/StackFrameLexer.cs b/src/Features/Core/Portable/EmbeddedLanguages/StackFrame/StackFrameLexer.cs index 0e9eb8a273910..3577bf1060937 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/StackFrame/StackFrameLexer.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/StackFrame/StackFrameLexer.cs @@ -432,7 +432,7 @@ private static ImmutableArray CreateTrivia(params StackFrameTr } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private readonly bool IsStringAtPosition(string val) diff --git a/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs b/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs index c3b7319cf330b..5715e2012bebf 100644 --- a/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs +++ b/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs @@ -70,7 +70,7 @@ public async Task> GetEncapsulateFieldCodeActionsAsyn } builder.AddRange(EncapsulateAllFields(document, fields, fallbackOptions)); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private ImmutableArray EncapsulateAllFields(Document document, ImmutableArray fields, CleanCodeGenerationOptionsProvider fallbackOptions) diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs index 003150858acf2..6d4e5c5512ffb 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs @@ -103,7 +103,7 @@ public async Task StartSessionAsync(Solution solution, ImmutableArray ca { if (commitUpdates) { - _encService.CommitSolutionUpdate(sessionId, out _); + _encService.CommitSolutionUpdate(sessionId); } else { @@ -135,7 +135,7 @@ public async Task StartSessionAsync(Solution solution, ImmutableArray ca public void EndSession() { Contract.ThrowIfFalse(_sessionId != default, "Session has not started"); - _encService.EndDebuggingSession(_sessionId, out _); + _encService.EndDebuggingSession(_sessionId); } internal TestAccessor GetTestAccessor() diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingSearchHelpers.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingSearchHelpers.cs index 3732e12669c7b..069c2bfa901bf 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingSearchHelpers.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingSearchHelpers.cs @@ -67,7 +67,7 @@ public static async Task> GetSourceLocat foreach (var location in locations.Value) result.AddIfNotNull(await location.TryRehydrateAsync(project.Solution, cancellationToken).ConfigureAwait(false)); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } return await GetSourceLocationsInProcessAsync(project, query, cancellationToken).ConfigureAwait(false); @@ -160,7 +160,7 @@ private static async Task> GetSourceLoca await foreach (var item in GetSourceLocationsInProcessWorkerAsync(project, query, cancellationToken).ConfigureAwait(false)) result.Add(item); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static IAsyncEnumerable GetSourceLocationsInProcessWorkerAsync( diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/DefaultUnitTestingDocumentTrackingService.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/DefaultUnitTestingDocumentTrackingService.cs deleted file mode 100644 index 97e279c6cb2a2..0000000000000 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/DefaultUnitTestingDocumentTrackingService.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler; - -[ExportWorkspaceService(typeof(IUnitTestingDocumentTrackingService), ServiceLayer.Default)] -[Shared] -internal sealed class DefaultUnitTestingDocumentTrackingService : IUnitTestingDocumentTrackingService -{ - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultUnitTestingDocumentTrackingService() - { - } - - public bool SupportsDocumentTracking => false; - - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } - - public ImmutableArray GetVisibleDocuments() - => []; - - public DocumentId? TryGetActiveDocument() - => null; -} diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingService.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingService.cs deleted file mode 100644 index bdd1f6262ce7d..0000000000000 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingService.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler; - -internal interface IUnitTestingDocumentTrackingService : 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. - /// - DocumentId? TryGetActiveDocument(); - - /// - /// Get a read only collection of the s of all the visible documents in the workspace. - /// - ImmutableArray GetVisibleDocuments(); - - /// - /// Raised when a text buffer that's not part of a workspace is changed. - /// - event EventHandler NonRoslynBufferTextChanged; -} diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingServiceExtensions.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingServiceExtensions.cs deleted file mode 100644 index 8d5735b865092..0000000000000 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingServiceExtensions.cs +++ /dev/null @@ -1,36 +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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler; - -internal static class IUnitTestingDocumentTrackingServiceExtensions -{ - /// - /// Gets the active the user is currently working in. May be null if - /// there is no active document or the active document is not in this . - /// - public static Document? GetActiveDocument(this IUnitTestingDocumentTrackingService service, Solution solution) - { - // Note: GetDocument checks that the DocId is contained in the solution, and returns null if not. - return solution.GetDocument(service.TryGetActiveDocument()); - } - - /// - /// Get a read only collection of all the unique visible documents in the workspace that are - /// contained within . - /// - public static ImmutableArray GetVisibleDocuments(this IUnitTestingDocumentTrackingService service, Solution solution) - => service.GetVisibleDocuments() - .Select(solution.GetDocument) - .WhereNotNull() - .Distinct() - .ToImmutableArray(); -} diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.AbstractUnitTestingPriorityProcessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.AbstractUnitTestingPriorityProcessor.cs index ddaa204b9282b..826660ba0a116 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.AbstractUnitTestingPriorityProcessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.AbstractUnitTestingPriorityProcessor.cs @@ -37,7 +37,6 @@ public AbstractUnitTestingPriorityProcessor( _lazyAnalyzers = lazyAnalyzers; Processor = processor; - Processor._documentTracker.NonRoslynBufferTextChanged += OnNonRoslynBufferTextChanged; } public ImmutableArray Analyzers @@ -117,31 +116,6 @@ protected async Task WaitForHigherPriorityOperationsAsync() } } } - - public override void Shutdown() - { - base.Shutdown(); - - Processor._documentTracker.NonRoslynBufferTextChanged -= OnNonRoslynBufferTextChanged; - } - - private void OnNonRoslynBufferTextChanged(object? sender, EventArgs e) - { - // There are 2 things incremental processor takes care of - // - // #1 is making sure we delay processing any work until there is enough idle (ex, typing) in host. - // #2 is managing cancellation and pending works. - // - // we used to do #1 and #2 only for Roslyn files. and that is usually fine since most of time solution contains only roslyn files. - // - // but for mixed solution (ex, Roslyn files + HTML + JS + CSS), #2 still makes sense but #1 doesn't. We want - // to pause any work while something is going on in other project types as well. - // - // we need to make sure we play nice with neighbors as well. - // - // now, we don't care where changes are coming from. if there is any change in host, we pause ourselves for a while. - UpdateLastAccessTime(); - } } } } diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingIncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingIncrementalAnalyzerProcessor.cs index 9f3ef2fd09d1c..7b883e0bed9fd 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingIncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingIncrementalAnalyzerProcessor.cs @@ -30,7 +30,6 @@ private partial class UnitTestingIncrementalAnalyzerProcessor private readonly UnitTestingRegistration _registration; private readonly IAsynchronousOperationListener _listener; - private readonly IUnitTestingDocumentTrackingService _documentTracker; private readonly UnitTestingNormalPriorityProcessor _normalPriorityProcessor; private readonly UnitTestingLowPriorityProcessor _lowPriorityProcessor; @@ -58,8 +57,6 @@ public UnitTestingIncrementalAnalyzerProcessor( var lazyAllAnalyzers = new Lazy>(() => GetIncrementalAnalyzers(_registration, analyzersGetter, onlyHighPriorityAnalyzer: false)); // event and worker queues - _documentTracker = _registration.Services.GetRequiredService(); - var globalNotificationService = _registration.Services.ExportProvider.GetExports().FirstOrDefault()?.Value; _normalPriorityProcessor = new UnitTestingNormalPriorityProcessor(listener, this, lazyAllAnalyzers, globalNotificationService, normalBackOffTimeSpan, shutdownToken); diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingLowPriorityProcessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingLowPriorityProcessor.cs index e7a622ffc754b..a9bf69e2f06c9 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingLowPriorityProcessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingLowPriorityProcessor.cs @@ -51,13 +51,8 @@ protected override async Task ExecuteAsync() // we wait for global operation, higher and normal priority processor to finish its working 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( - preferableProjectId, + preferableProjectId: null, out var workItem, out var projectCancellation)) { await ProcessProjectAsync(Analyzers, workItem, projectCancellation).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingNormalPriorityProcessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingNormalPriorityProcessor.cs index 9c6186807918a..9c8fb46ddf534 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingNormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingNormalPriorityProcessor.cs @@ -120,12 +120,6 @@ protected override async Task ExecuteAsync() // okay, there must be at least one item in the map ResetStates(); - if (await TryProcessOneHigherPriorityDocumentAsync().ConfigureAwait(false)) - { - // successfully processed a high priority document. - return; - } - // process one of documents remaining if (!_workItemQueue.TryTakeAnyWork( _currentProjectProcessing, @@ -175,77 +169,6 @@ private void SetProjectProcessing(ProjectId currentProject) _currentProjectProcessing = currentProject; } - private IEnumerable GetPrioritizedPendingDocuments() - { - // First the active document - var activeDocumentId = Processor._documentTracker.TryGetActiveDocument(); - if (activeDocumentId != null) - { - yield return activeDocumentId; - } - - // Now any visible documents - foreach (var visibleDocumentId in Processor._documentTracker.GetVisibleDocuments()) - { - yield return visibleDocumentId; - } - - // Any other high priority documents - foreach (var (documentId, _) in _higherPriorityDocumentsNotProcessed) - { - yield return documentId; - } - } - - private async Task TryProcessOneHigherPriorityDocumentAsync() - { - try - { - if (!Processor._documentTracker.SupportsDocumentTracking) - { - return false; - } - - foreach (var documentId in GetPrioritizedPendingDocuments()) - { - if (CancellationToken.IsCancellationRequested) - { - return true; - } - - // this is a best effort algorithm with some shortcomings. - // - // the most obvious issue is if there is a new work item (without a solution change - but very unlikely) - // for a opened document we already processed, the work item will be treated as a regular one rather than higher priority one - // (opened document) - // see whether we have work item for the document - if (!_workItemQueue.TryTake(documentId, out var workItem, out var documentCancellation)) - { - RemoveHigherPriorityDocument(documentId); - continue; - } - - // okay now we have work to do - await ProcessDocumentAsync(Analyzers, workItem, documentCancellation).ConfigureAwait(false); - - RemoveHigherPriorityDocument(documentId); - return true; - } - - return false; - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) - { - throw ExceptionUtilities.Unreachable(); - } - } - - private void RemoveHigherPriorityDocument(DocumentId documentId) - { - // remove opened document processed - _higherPriorityDocumentsNotProcessed.TryRemove(documentId, out _); - } - private async Task ProcessDocumentAsync(ImmutableArray analyzers, UnitTestingWorkItem workItem, CancellationToken cancellationToken) { Contract.ThrowIfNull(workItem.DocumentId); diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.cs index 006826cd33f57..bccd7842bc6fe 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.cs @@ -524,8 +524,8 @@ public UnitTestingReanalyzeScope(SolutionId solutionId) public UnitTestingReanalyzeScope(IEnumerable? projectIds = null, IEnumerable? documentIds = null) { - projectIds ??= SpecializedCollections.EmptyEnumerable(); - documentIds ??= SpecializedCollections.EmptyEnumerable(); + projectIds ??= []; + documentIds ??= []; _solutionId = null; _projectOrDocumentIds = new HashSet(projectIds); diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptDiagnosticAnalyzerService.cs index d753d54cfda06..5a3ee516cae7a 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptDiagnosticAnalyzerService.cs @@ -8,5 +8,11 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; internal interface IVSTypeScriptDiagnosticAnalyzerService { - void Reanalyze(Workspace workspace, IEnumerable? projectIds = null, IEnumerable? documentIds = null, bool highPriority = false); + /// + /// Issues a request to invalidate all diagnostics reported to the LSP client, and have them all be + /// recomputed. This is equivalent to an LSP diagnostic refresh request. This should be used sparingly. For + /// example: when a user changes an option controlling diagnostics. Note: all arguments are unused and are only + /// kept around for legacy binary compat purposes. + /// + void Reanalyze(Workspace? workspace = null, IEnumerable? projectIds = null, IEnumerable? documentIds = null, bool highPriority = false); } diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptDiagnosticService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptDiagnosticService.cs deleted file mode 100644 index 4fd9cf9810021..0000000000000 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/IVSTypeScriptDiagnosticService.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; - -internal interface IVSTypeScriptDiagnosticService -{ - Task> GetPushDiagnosticsAsync(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics, CancellationToken cancellationToken); - - [Obsolete] - IDisposable RegisterDiagnosticsUpdatedEventHandler(Action action); - - IDisposable RegisterDiagnosticsUpdatedEventHandler(Action> action); -} diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs index c7c129b9c41ac..305b32beb9fdd 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs @@ -19,6 +19,6 @@ internal sealed class VSTypeScriptAnalyzerService(IDiagnosticAnalyzerService ser { private readonly IDiagnosticAnalyzerService _service = service; - public void Reanalyze(Workspace workspace, IEnumerable? projectIds = null, IEnumerable? documentIds = null, bool highPriority = false) - => _service.Reanalyze(workspace, projectIds, documentIds, highPriority); + public void Reanalyze(Workspace? workspace, IEnumerable? projectIds, IEnumerable? documentIds, bool highPriority) + => _service.RequestDiagnosticRefresh(); } diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs index 55600bdde6add..64d222b91d20f 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs @@ -35,14 +35,14 @@ public async Task SearchDocumentAsync( Document document, string searchPattern, IImmutableSet kinds, - Func onResultFound, + Func, Task> onResultsFound, CancellationToken cancellationToken) { if (_searchService != null) { var results = await _searchService.SearchDocumentAsync(document, searchPattern, kinds, cancellationToken).ConfigureAwait(false); - foreach (var result in results) - await onResultFound(Convert(result)).ConfigureAwait(false); + if (results.Length > 0) + await onResultsFound(results.SelectAsArray(Convert)).ConfigureAwait(false); } } @@ -53,7 +53,7 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -78,8 +78,9 @@ async Task ProcessProjectAsync(Project project) { var results = await _searchService.SearchProjectAsync( project, priorityDocuments.WhereAsArray(d => d.Project == project), searchPattern, kinds, cancellationToken).ConfigureAwait(false); - foreach (var result in results) - await onResultFound(project, Convert(result)).ConfigureAwait(false); + + if (results.Length > 0) + await onResultsFound(results.SelectAsArray(Convert)).ConfigureAwait(false); } await onProjectCompleted().ConfigureAwait(false); diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index 1dd4931dd871d..f715ff1338f30 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Immutable; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; @@ -14,12 +13,10 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Watch.Api; -internal sealed class WatchHotReloadService +internal sealed class WatchHotReloadService(SolutionServices services, Func>> capabilitiesProvider) { - private sealed class DebuggerService(ImmutableArray capabilities) : IManagedHotReloadService + private sealed class DebuggerService(Func>> capabilitiesProvider) : IManagedHotReloadService { - private readonly ImmutableArray _capabilities = capabilities; - public ValueTask> GetActiveStatementsAsync(CancellationToken cancellationToken) => ValueTaskFactory.FromResult(ImmutableArray.Empty); @@ -27,41 +24,46 @@ public ValueTask GetAvailabilityAsync(Guid module, => ValueTaskFactory.FromResult(new ManagedHotReloadAvailability(ManagedHotReloadAvailabilityStatus.Available)); public ValueTask> GetCapabilitiesAsync(CancellationToken cancellationToken) - => ValueTaskFactory.FromResult(_capabilities); + => capabilitiesProvider(); public ValueTask PrepareModuleForUpdateAsync(Guid module, CancellationToken cancellationToken) => ValueTaskFactory.CompletedTask; } - public readonly struct Update + public readonly struct Update( + Guid moduleId, + ImmutableArray ilDelta, + ImmutableArray metadataDelta, + ImmutableArray pdbDelta, + ImmutableArray updatedTypes, + ImmutableArray requiredCapabilities) { - public readonly Guid ModuleId; - public readonly ImmutableArray ILDelta; - public readonly ImmutableArray MetadataDelta; - public readonly ImmutableArray PdbDelta; - public readonly ImmutableArray UpdatedTypes; - public readonly ImmutableArray RequiredCapabilities; - - internal Update(Guid moduleId, ImmutableArray ilDelta, ImmutableArray metadataDelta, ImmutableArray pdbDelta, ImmutableArray updatedTypes, ImmutableArray requiredCapabilities) - { - ModuleId = moduleId; - ILDelta = ilDelta; - MetadataDelta = metadataDelta; - PdbDelta = pdbDelta; - UpdatedTypes = updatedTypes; - RequiredCapabilities = requiredCapabilities; - } + public readonly Guid ModuleId = moduleId; + public readonly ImmutableArray ILDelta = ilDelta; + public readonly ImmutableArray MetadataDelta = metadataDelta; + public readonly ImmutableArray PdbDelta = pdbDelta; + public readonly ImmutableArray UpdatedTypes = updatedTypes; + public readonly ImmutableArray RequiredCapabilities = requiredCapabilities; } private static readonly ActiveStatementSpanProvider s_solutionActiveStatementSpanProvider = (_, _, _) => ValueTaskFactory.FromResult(ImmutableArray.Empty); - private readonly IEditAndContinueService _encService; + private readonly IEditAndContinueService _encService = services.GetRequiredService().Service; + private DebuggingSessionId _sessionId; - private readonly ImmutableArray _capabilities; public WatchHotReloadService(HostWorkspaceServices services, ImmutableArray capabilities) - => (_encService, _capabilities) = (services.GetRequiredService().Service, capabilities); + : this(services.SolutionServices, () => ValueTaskFactory.FromResult(AddImplicitDotNetCapabilities(capabilities))) + { + } + + /// + /// Adds capabilities that are available by default on runtimes supported by dotnet-watch: .NET and Mono + /// and not on .NET Framework (they are not in . + /// + private static ImmutableArray AddImplicitDotNetCapabilities(ImmutableArray capabilities) + => capabilities.Add(nameof(EditAndContinueCapabilities.AddExplicitInterfaceImplementation)); /// /// Starts the watcher. @@ -72,7 +74,7 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell { var newSessionId = await _encService.StartDebuggingSessionAsync( solution, - new DebuggerService(_capabilities), + new DebuggerService(capabilitiesProvider), NullPdbMatchingSourceTextProvider.Instance, captureMatchingDocuments: [], captureAllMatchingDocuments: true, @@ -82,6 +84,17 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell _sessionId = newSessionId; } + /// + /// Invoke when capabilities have changed. + /// + public void CapabilitiesChanged() + { + var sessionId = _sessionId; + Contract.ThrowIfFalse(sessionId != default, "Session has not started"); + + _encService.BreakStateOrCapabilitiesChanged(sessionId, inBreakState: null); + } + /// /// Emits updates for all projects that differ between the given snapshot and the one given to the previous successful call or /// the one passed to for the first invocation. @@ -100,7 +113,7 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell if (results.ModuleUpdates.Status == ModuleUpdateStatus.Ready) { - _encService.CommitSolutionUpdate(sessionId, out _); + _encService.CommitSolutionUpdate(sessionId); } var updates = results.ModuleUpdates.Updates.SelectAsArray( @@ -114,7 +127,8 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell public void EndSession() { Contract.ThrowIfFalse(_sessionId != default, "Session has not started"); - _encService.EndDebuggingSession(_sessionId, out _); + _encService.EndDebuggingSession(_sessionId); + _sessionId = default; } internal TestAccessor GetTestAccessor() diff --git a/src/Features/Core/Portable/ExtractClass/ExtractClassWithDialogCodeAction.cs b/src/Features/Core/Portable/ExtractClass/ExtractClassWithDialogCodeAction.cs index 86ecfc7723b81..4fa3b561c7aa5 100644 --- a/src/Features/Core/Portable/ExtractClass/ExtractClassWithDialogCodeAction.cs +++ b/src/Features/Core/Portable/ExtractClass/ExtractClassWithDialogCodeAction.cs @@ -61,11 +61,9 @@ protected sealed override CodeActionPriority ComputePriority() protected override async Task> ComputeOperationsAsync( object options, IProgress progressTracker, CancellationToken cancellationToken) { + // If user click cancel button, options will be null and hit this branch if (options is not ExtractClassOptions extractClassOptions) - { - // If user click cancel button, options will be null and hit this branch - return SpecializedCollections.EmptyEnumerable(); - } + return []; // Map the symbols we're removing to annotations // so we can find them easily diff --git a/src/Features/Core/Portable/ExtractInterface/AbstractExtractInterfaceService.cs b/src/Features/Core/Portable/ExtractInterface/AbstractExtractInterfaceService.cs index db2ae0e7e12ca..12684fa5f487d 100644 --- a/src/Features/Core/Portable/ExtractInterface/AbstractExtractInterfaceService.cs +++ b/src/Features/Core/Portable/ExtractInterface/AbstractExtractInterfaceService.cs @@ -439,7 +439,7 @@ private static ImmutableArray CreateInterfaceMembers(IEnumerable(), LeadingTrivia = trivia }; - } + return new LeadingTrailingTriviaPair { TrailingTrivia = [], LeadingTrivia = trivia }; return GetTrailingAndLeadingTrivia(trivia); } @@ -275,10 +273,10 @@ private static IEnumerable ResolveTrivia( Dictionary triviaMap) { triviaMap.TryGetValue(tokenPair.PreviousToken, out var previousTriviaPair); - var trailingTrivia = previousTriviaPair.TrailingTrivia ?? SpecializedCollections.EmptyEnumerable(); + var trailingTrivia = previousTriviaPair.TrailingTrivia ?? []; triviaMap.TryGetValue(tokenPair.NextToken, out var nextTriviaPair); - var leadingTrivia = nextTriviaPair.LeadingTrivia ?? SpecializedCollections.EmptyEnumerable(); + var leadingTrivia = nextTriviaPair.LeadingTrivia ?? []; return tokenPair.PreviousToken.TrailingTrivia.Concat(trailingTrivia).Concat(leadingTrivia).Concat(tokenPair.NextToken.LeadingTrivia); } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs index 0114fd1847205..4c25aeab0f33a 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs @@ -2,12 +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. -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.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; @@ -440,11 +438,10 @@ private ImmutableArray MarkVariableInfoToUseAsReturnValueIfPossibl private static ImmutableArray GetMethodParameters(Dictionary variableInfoMap) { - using var _ = ArrayBuilder.GetInstance(variableInfoMap.Count, out var list); + var list = new FixedSizeArrayBuilder(variableInfoMap.Count); list.AddRange(variableInfoMap.Values); - list.Sort(); - return list.ToImmutable(); + return list.MoveToImmutable(); } /// When false, variables whose data flow is not understood @@ -887,21 +884,15 @@ private static IEnumerable AppendTypeParametersInConstrain ITypeSymbol type, HashSet visited) { if (visited.Contains(type)) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; visited.Add(type); if (type.OriginalDefinition.Equals(type)) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (type is not INamedTypeSymbol constructedType) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var parameters = constructedType.GetAllTypeParameters().ToList(); var arguments = constructedType.GetAllTypeArguments().ToList(); diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs index 7b8c914018368..609df59688867 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs @@ -288,7 +288,7 @@ protected ImmutableArray AddSplitOrMoveDeclarationOutStatement list.Add(declaration); } - return list.ToImmutable(); + return list.ToImmutableAndClear(); } protected ImmutableArray AppendReturnStatementIfNeeded(ImmutableArray statements) diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs index af01a2f0785ee..349b799dcf13d 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs @@ -214,7 +214,7 @@ private static async Task ExpandAsync(TSelectionResult selecti } private ImmutableArray GetFormattingRules(Document document) - => ImmutableArray.Create(GetCustomFormattingRule(document)).AddRange(Formatter.GetDefaultFormattingRules(document)); + => [GetCustomFormattingRule(document), .. Formatter.GetDefaultFormattingRules(document)]; private OperationStatus CheckVariableTypes( OperationStatus status, diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 24903f5221c28..89a689d9c110b 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -3231,4 +3231,34 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Dismiss + + Symbols + + + Semantic Search + + + Query + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + Semantic search query terminated with exception + + + Semantic search query failed to compile + + + The query does not specify '{0}' method or top-level function + + + Method '{0}' must be static and non-generic + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + + Unable to load type '{0}': '{1}' + \ No newline at end of file diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs index b5c8b72f60d31..30c8390088926 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs @@ -59,7 +59,7 @@ public ImmutableArray GetDefinitions() { lock (_gate) { - return _definitions.ToImmutableArray(); + return [.. _definitions]; } } } diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs index 232b266014dab..4e3563c936b61 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs @@ -5,6 +5,7 @@ #nullable disable using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; @@ -73,8 +74,6 @@ public IStreamingProgressTracker ProgressTracker // any of these. 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 @@ -107,17 +106,21 @@ public async ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationTok await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); } - public async ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation location, CancellationToken cancellationToken) + public async ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - var definitionItem = await GetDefinitionItemAsync(group, cancellationToken).ConfigureAwait(false); - var referenceItem = await location.TryCreateSourceReferenceItemAsync( - classificationOptions, - definitionItem, - includeHiddenLocations: false, - cancellationToken).ConfigureAwait(false); - - if (referenceItem != null) - await context.OnReferenceFoundAsync(referenceItem, cancellationToken).ConfigureAwait(false); + foreach (var (group, _, location) in references) + { + var definitionItem = await GetDefinitionItemAsync(group, cancellationToken).ConfigureAwait(false); + var referenceItem = await location.TryCreateSourceReferenceItemAsync( + classificationOptions, + definitionItem, + includeHiddenLocations: false, + cancellationToken).ConfigureAwait(false); + + if (referenceItem != null) + await context.OnReferenceFoundAsync(referenceItem, cancellationToken).ConfigureAwait(false); + } } } } diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs index 18ef1f1dd5451..9e2ff048580f5 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs @@ -120,7 +120,7 @@ private static async Task> FindSourceImplementationsAsyn } } - return builder.ToImmutableArray(); + return [.. builder]; static bool AddedAllLocations(ISymbol implementation, HashSet<(string filePath, TextSpan span)> seenLocations) { @@ -153,7 +153,7 @@ private static async Task> FindImplementationsWorkerAsyn } } - return result.ToImmutableArray(); + return [.. result]; } private static async Task> FindSourceAndMetadataImplementationsAsync( @@ -189,7 +189,7 @@ private static async Task> FindSourceAndMetadataImplemen implementationsAndOverrides.Add(symbol); } - return implementationsAndOverrides.ToImmutableArray(); + return [.. implementationsAndOverrides]; } else if (symbol is INamedTypeSymbol { TypeKind: TypeKind.Class } namedType) { diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindReferences.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindReferences.cs index 89d32557d8d87..b3dc369273577 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindReferences.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindReferences.cs @@ -81,7 +81,7 @@ private static async Task> GetThirdPartyDefinitio result.AddIfNotNull(thirdParty); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static async Task FindSymbolReferencesAsync( diff --git a/src/Features/Core/Portable/GenerateComparisonOperators/GenerateComparisonOperatorsCodeRefactoringProvider.cs b/src/Features/Core/Portable/GenerateComparisonOperators/GenerateComparisonOperatorsCodeRefactoringProvider.cs index 37b6b8f905968..677bfa87e2373 100644 --- a/src/Features/Core/Portable/GenerateComparisonOperators/GenerateComparisonOperatorsCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/GenerateComparisonOperators/GenerateComparisonOperatorsCodeRefactoringProvider.cs @@ -205,7 +205,7 @@ private static ImmutableArray GenerateComparisonOperators( } } - return operators.ToImmutable(); + return operators.ToImmutableAndClear(); } private static SyntaxNode GenerateStatement( diff --git a/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.GenerateConstructorWithDialogCodeAction.cs b/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.GenerateConstructorWithDialogCodeAction.cs index 6b17db6afc991..1b89718070601 100644 --- a/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.GenerateConstructorWithDialogCodeAction.cs +++ b/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.GenerateConstructorWithDialogCodeAction.cs @@ -57,9 +57,7 @@ protected override async Task> ComputeOperation { var result = (PickMembersResult)options; if (result.IsCanceled) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var addNullChecksOption = result.Options.FirstOrDefault(o => o.Id == AddNullChecksId); if (addNullChecksOption != null) @@ -79,9 +77,7 @@ protected override async Task> ComputeOperation result.Members, _fallbackOptions, cancellationToken).ConfigureAwait(false); if (state == null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; // There was an existing constructor that matched what the user wants to create. // Generate it if it's the implicit, no-arg, constructor, otherwise just navigate diff --git a/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs b/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs index c928cddf866f9..842c90a3eb6e8 100644 --- a/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PickMembers; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Simplification; @@ -104,7 +105,7 @@ await ComputeRefactoringsAsync( results.AddIfNotNull(intentResult); } - return results.ToImmutable(); + return results.ToImmutableAndClear(); static async Task GetIntentProcessorResultAsync( Document priorDocument, CodeAction codeAction, IProgress progressTracker, CancellationToken cancellationToken) @@ -274,13 +275,13 @@ public async Task> GenerateConstructorFromMembersAsyn private ImmutableArray GetCodeActions(Document document, State state, bool addNullChecks, CleanCodeGenerationOptionsProvider fallbackOptions) { - using var _ = ArrayBuilder.GetInstance(out var result); + using var result = TemporaryArray.Empty; result.Add(new FieldDelegatingCodeAction(this, document, state, addNullChecks, fallbackOptions)); if (state.DelegatedConstructor != null) result.Add(new ConstructorDelegatingCodeAction(this, document, state, addNullChecks, fallbackOptions)); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static async Task AddNavigationAnnotationAsync(Document document, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/GenerateDefaultConstructors/AbstractGenerateDefaultConstructorsService.cs b/src/Features/Core/Portable/GenerateDefaultConstructors/AbstractGenerateDefaultConstructorsService.cs index 0a44b64ad45b7..40bbda4c13000 100644 --- a/src/Features/Core/Portable/GenerateDefaultConstructors/AbstractGenerateDefaultConstructorsService.cs +++ b/src/Features/Core/Portable/GenerateDefaultConstructors/AbstractGenerateDefaultConstructorsService.cs @@ -50,7 +50,7 @@ public async Task> GenerateDefaultConstructorsAsync( } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } } diff --git a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs index 5ed4e014de204..590f45b35393c 100644 --- a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs @@ -236,7 +236,7 @@ private async Task> CreateActionsAsync( } var codeActions = await Task.WhenAll(tasks).ConfigureAwait(false); - return codeActions.ToImmutableArray(); + return [.. codeActions]; } diff --git a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndHashWithDialogCodeAction.cs b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndHashWithDialogCodeAction.cs index ea795e1ab5782..755f08914b06a 100644 --- a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndHashWithDialogCodeAction.cs +++ b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndHashWithDialogCodeAction.cs @@ -56,7 +56,7 @@ protected override async Task> ComputeOperation { var result = (PickMembersResult)options; if (result.IsCanceled) - return SpecializedCollections.EmptyEnumerable(); + return []; var solution = _document.Project.Solution; diff --git a/src/Features/Core/Portable/GenerateFromMembers/AbstractGenerateFromMembersCodeRefactoringProvider.cs b/src/Features/Core/Portable/GenerateFromMembers/AbstractGenerateFromMembersCodeRefactoringProvider.cs index 1a5d71ab6c46f..26eb5f27584f4 100644 --- a/src/Features/Core/Portable/GenerateFromMembers/AbstractGenerateFromMembersCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/GenerateFromMembers/AbstractGenerateFromMembersCodeRefactoringProvider.cs @@ -115,7 +115,7 @@ protected static ImmutableArray DetermineParameters( name: parameterName)); } - return parameters.ToImmutable(); + return parameters.ToImmutableAndClear(); } protected static readonly SymbolDisplayFormat SimpleFormat = diff --git a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs index 2b673dbdbcb49..eef382a09391a 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Utilities; @@ -87,7 +88,7 @@ public async Task> GenerateConstructorAsync(Document { Contract.ThrowIfNull(state.TypeToGenerateIn); - using var _ = ArrayBuilder.GetInstance(out var result); + using var result = TemporaryArray.Empty; // If we have any fields we'd like to generate, offer a code action to do that. if (state.ParameterToNewFieldMap.Count > 0) @@ -113,7 +114,7 @@ public async Task> GenerateConstructorAsync(Document c => state.GetChangedDocumentAsync(document, withFields: false, withProperties: false, c), nameof(FeaturesResources.Generate_constructor_in_0) + "_" + state.TypeToGenerateIn.Name)); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateMethodService.State.cs b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateMethodService.State.cs index 108f79783a531..326c90abfad9b 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateMethodService.State.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateMethodService.State.cs @@ -242,7 +242,7 @@ private static ImmutableArray GenerateParameterNamesBasedOnParameterType } NameGenerator.EnsureUniquenessInPlace(names, isFixed); - return names.ToImmutable(); + return names.ToImmutableAndClear(); } private static IMethodSymbol CreateMethodSymbolWithReturnType( diff --git a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs index 8ffdab80fa802..7cd296978ce7b 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs @@ -62,7 +62,7 @@ private ITypeParameterSymbol MassageTypeParameter( var nonClassTypes = constraints.Where(ts => ts.TypeKind != TypeKind.Class).ToList(); classTypes = MergeClassTypes(classTypes); - constraints = classTypes.Concat(nonClassTypes).ToList(); + constraints = [.. classTypes, .. nonClassTypes]; if (constraints.SequenceEqual(typeParameter.ConstraintTypes)) { return typeParameter; diff --git a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs index 9641bb4d73d38..4f586c5d254f0 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs @@ -204,7 +204,7 @@ private async ValueTask> DetermineParametersAsy var optionality = DetermineParameterOptionality(cancellationToken); var names = DetermineParameterNames(cancellationToken); - using var _ = ArrayBuilder.GetInstance(out var result); + var result = new FixedSizeArrayBuilder(modifiers.Length); for (var i = 0; i < modifiers.Length; i++) { result.Add(CodeGenerationSymbolFactory.CreateParameterSymbol( @@ -216,7 +216,7 @@ private async ValueTask> DetermineParametersAsy name: names[i].BestNameForParameter)); } - return result.ToImmutable(); + return result.MoveToImmutable(); } private Accessibility DetermineAccessibility(bool isAbstract) diff --git a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.cs b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.cs index 4e7f7c1c47d7d..b5b1561c3b3a5 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.cs @@ -71,6 +71,6 @@ protected async ValueTask> GetActionsAsync(Document d } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/GenerateMember/GenerateVariable/AbstractGenerateVariableService.GenerateLocalCodeAction.cs b/src/Features/Core/Portable/GenerateMember/GenerateVariable/AbstractGenerateVariableService.GenerateLocalCodeAction.cs index 8c04c0fa758e8..89c3b25fa84a7 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateVariable/AbstractGenerateVariableService.GenerateLocalCodeAction.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateVariable/AbstractGenerateVariableService.GenerateLocalCodeAction.cs @@ -67,11 +67,7 @@ private async Task GetNewRootAsync(CancellationToken cancellationTok var context = new CodeGenerationContext(beforeThisLocation: _state.IdentifierToken.GetLocation()); var info = await _document.GetCodeGenerationInfoAsync(context, _fallbackOptions, cancellationToken).ConfigureAwait(false); - return info.Service.AddStatements( - root, - SpecializedCollections.SingletonEnumerable(localStatement), - info, - cancellationToken: cancellationToken); + return info.Service.AddStatements(root, [localStatement], info, cancellationToken); } } } diff --git a/src/Features/Core/Portable/GenerateMember/GenerateVariable/AbstractGenerateVariableService.cs b/src/Features/Core/Portable/GenerateMember/GenerateVariable/AbstractGenerateVariableService.cs index f1172510dd147..3a3b069c83707 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateVariable/AbstractGenerateVariableService.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateVariable/AbstractGenerateVariableService.cs @@ -87,7 +87,7 @@ public async Task> GenerateVariableAsync( isInlinable: true)]; } - return actions.ToImmutable(); + return actions.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/GenerateOverrides/GenerateOverridesWithDialogCodeAction.cs b/src/Features/Core/Portable/GenerateOverrides/GenerateOverridesWithDialogCodeAction.cs index 326aef8fce1bc..dafdd480c971e 100644 --- a/src/Features/Core/Portable/GenerateOverrides/GenerateOverridesWithDialogCodeAction.cs +++ b/src/Features/Core/Portable/GenerateOverrides/GenerateOverridesWithDialogCodeAction.cs @@ -54,7 +54,7 @@ protected override async Task> ComputeOperation { var result = (PickMembersResult)options; if (result.IsCanceled || result.Members.Length == 0) - return SpecializedCollections.EmptyEnumerable(); + return []; var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); RoslynDebug.AssertNotNull(syntaxTree); diff --git a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs index f49546834690b..8adaaeda55199 100644 --- a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs +++ b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs @@ -313,7 +313,7 @@ private async Task> GetGenerateInNewFileOper ? folders : _state.SimpleName != _state.NameOrMemberAccessExpression ? containers.ToList() - : _semanticDocument.Document.Folders.ToList(); + : [.. _semanticDocument.Document.Folders]; if (newDocument.Project.Language == _semanticDocument.Document.Project.Language) { @@ -480,9 +480,7 @@ private async Task> GetGenerateIntoExistingD { string includeUsingsOrImports = null; if (!areFoldersValidIdentifiers) - { - folders = SpecializedCollections.EmptyList(); - } + folders = []; // Now actually create the symbol that we want to add to the root namespace. The // symbol may either be a named type (if we're not generating into a namespace) or @@ -526,8 +524,8 @@ private async Task> GetGenerateIntoExistingD // Populate the ContainerList AddFoldersToNamespaceContainers(containerList, folders); - containers = containerList.ToArray(); - includeUsingsOrImports = string.Join(".", containerList.ToArray()); + containers = [.. containerList]; + includeUsingsOrImports = string.Join(".", [.. containerList]); } // Case 4 : If the type is generated into the same VB project or @@ -540,8 +538,8 @@ private async Task> GetGenerateIntoExistingD { // Populate the ContainerList AddFoldersToNamespaceContainers(containerList, folders); - containers = containerList.ToArray(); - includeUsingsOrImports = string.Join(".", containerList.ToArray()); + containers = [.. containerList]; + includeUsingsOrImports = string.Join(".", [.. containerList]); if (!string.IsNullOrWhiteSpace(rootNamespaceOfTheProjectGeneratedInto)) { includeUsingsOrImports = string.IsNullOrEmpty(includeUsingsOrImports) diff --git a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.GenerateNamedType.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.GenerateNamedType.cs index c1e832b84c41f..f2bc225c4db8a 100644 --- a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.GenerateNamedType.cs +++ b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.GenerateNamedType.cs @@ -109,7 +109,7 @@ private async Task> DetermineMembersAsync(GenerateTypeOp if (_state.IsException) AddExceptionConstructors(members); - return members.ToImmutable(); + return members.ToImmutableAndClear(); } private async Task AddMembersAsync(ArrayBuilder members, GenerateTypeOptionsResult options = null) @@ -236,7 +236,7 @@ private void AddExceptionConstructors(ArrayBuilder members) var exceptionType = _semanticDocument.SemanticModel.Compilation.ExceptionType(); var constructors = exceptionType.InstanceConstructors - .Where(c => c.DeclaredAccessibility is Accessibility.Public or Accessibility.Protected) + .Where(c => c.DeclaredAccessibility is Accessibility.Public or Accessibility.Protected && !c.IsObsolete()) .Select(c => CodeGenerationSymbolFactory.CreateConstructorSymbol( attributes: default, accessibility: c.DeclaredAccessibility, @@ -321,7 +321,7 @@ protected IList GetAvailableTypeParameters() var availableInnerTypeParameters = _service.GetTypeParameters(_state, _semanticDocument.SemanticModel, _cancellationToken); var availableOuterTypeParameters = !_intoNamespace && _state.TypeToGenerateInOpt != null ? _state.TypeToGenerateInOpt.GetAllTypeParameters() - : SpecializedCollections.EmptyEnumerable(); + : []; return availableOuterTypeParameters.Concat(availableInnerTypeParameters).ToList(); } diff --git a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs index f2e55c928e008..61df8079a4dbb 100644 --- a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs +++ b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs @@ -140,7 +140,7 @@ private ImmutableArray GetActions( if (generateNewTypeInDialog) result.Add(new GenerateTypeCodeActionWithOption((TService)this, document.Document, state, fallbackOptions)); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static bool CanGenerateIntoContainingNamespace(SemanticDocument semanticDocument, SyntaxNode node, CancellationToken cancellationToken) @@ -234,7 +234,7 @@ protected static ImmutableArray GetTypeParameters( typeParameters[i] = CodeGenerationSymbolFactory.CreateTypeParameterSymbol(names[i]); } - return typeParameters.ToImmutable(); + return typeParameters.ToImmutableAndClear(); } protected static Accessibility DetermineDefaultAccessibility( @@ -272,7 +272,7 @@ protected IList GetAvailableTypeParameters( var availableInnerTypeParameters = GetTypeParameters(state, semanticModel, cancellationToken); var availableOuterTypeParameters = !intoNamespace && state.TypeToGenerateInOpt != null ? state.TypeToGenerateInOpt.GetAllTypeParameters() - : SpecializedCollections.EmptyEnumerable(); + : []; return availableOuterTypeParameters.Concat(availableInnerTypeParameters).ToList(); } diff --git a/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs b/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs index 28ced7c05cc82..851d23ed45f4c 100644 --- a/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs +++ b/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs @@ -44,7 +44,7 @@ await context.ReportNoResultsAsync( var (symbol, project) = symbolAndProjectOpt.Value; var solution = project.Solution; - var bases = await FindBaseHelpers.FindBasesAsync(symbol, solution, cancellationToken).ConfigureAwait(false); + var bases = FindBaseHelpers.FindBases(symbol, solution, cancellationToken); if (bases.Length == 0 && symbol is IMethodSymbol { MethodKind: MethodKind.Constructor } constructor) { var nextConstructor = await FindNextConstructorInChainAsync(solution, constructor, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs b/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs index b7ec56faeae7f..25a40bdc1271f 100644 --- a/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs +++ b/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.GoToBase; internal static class FindBaseHelpers { - public static ValueTask> FindBasesAsync( + public static ImmutableArray FindBases( ISymbol symbol, Solution solution, CancellationToken cancellationToken) { if (symbol is INamedTypeSymbol @@ -23,16 +23,12 @@ public static ValueTask> FindBasesAsync( } namedTypeSymbol) { var result = BaseTypeFinder.FindBaseTypesAndInterfaces(namedTypeSymbol).CastArray(); - return ValueTaskFactory.FromResult(result); + return result; } - if (symbol.Kind is SymbolKind.Property or - SymbolKind.Method or - SymbolKind.Event) - { - return BaseTypeFinder.FindOverriddenAndImplementedMembersAsync(symbol, solution, cancellationToken); - } + if (symbol.Kind is SymbolKind.Property or SymbolKind.Method or SymbolKind.Event) + return BaseTypeFinder.FindOverriddenAndImplementedMembers(symbol, solution, cancellationToken); - return ValueTaskFactory.FromResult(ImmutableArray.Empty); + return []; } } diff --git a/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs b/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs index 8320436f1d185..a28e87d1ad407 100644 --- a/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs +++ b/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs @@ -100,6 +100,6 @@ public static async Task> GetDefinitionsAsync( } definitions.Add(definitionItem); - return definitions.ToImmutable(); + return definitions.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs b/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs index c07f15bf3c071..f304e08c863d0 100644 --- a/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs +++ b/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs @@ -55,7 +55,7 @@ internal sealed class ImplementAbstractClassData( return null; var unimplementedMembers = classType.GetAllUnimplementedMembers( - SpecializedCollections.SingletonEnumerable(abstractClassType), + [abstractClassType], includeMembersRequiringExplicitImplementation: false, cancellationToken); @@ -288,7 +288,7 @@ private bool ShouldGenerateAccessor([NotNullWhen(true)] IMethodSymbol? method) } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static bool InheritsFromOrEquals(ITypeSymbol type, ITypeSymbol baseType) diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs index 425bfac34f9a4..0a02004557f60 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs @@ -262,7 +262,7 @@ private ImmutableArray GenerateMembers( } } - return implementedMembers.ToImmutable(); + return implementedMembers.ToImmutableAndClear(); } private bool IsReservedName(string name) @@ -307,7 +307,7 @@ private string DetermineMemberName(ISymbol member, ArrayBuilder impleme // // In this case we only want to generate 'Goo' once. if (HasMatchingMember(implementedVisibleMembers, member)) - return SpecializedCollections.EmptyEnumerable(); + return []; var memberName = DetermineMemberName(member, implementedVisibleMembers); diff --git a/src/Features/Core/Portable/ImplementInterface/ImplementHelpers.cs b/src/Features/Core/Portable/ImplementInterface/ImplementHelpers.cs index 86e490d30d974..434e73d7db9f9 100644 --- a/src/Features/Core/Portable/ImplementInterface/ImplementHelpers.cs +++ b/src/Features/Core/Portable/ImplementInterface/ImplementHelpers.cs @@ -36,11 +36,7 @@ public static ImmutableArray GetDelegatableMembers( var parameters = GetNonCapturedPrimaryConstructorParameters(fields, properties); - using var _1 = ArrayBuilder.GetInstance(fields.Length + properties.Length + parameters.Length, out var result); - result.AddRange(fields); - result.AddRange(properties); - result.AddRange(parameters); - return result.ToImmutableAndClear(); + return [.. fields, .. properties, .. parameters]; ImmutableArray GetNonCapturedPrimaryConstructorParameters( ImmutableArray fields, @@ -62,7 +58,7 @@ ImmutableArray GetNonCapturedPrimaryConstructorParameters( } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } bool IsAssignedToFieldOrProperty(ImmutableArray fields, ImmutableArray properties, IParameterSymbol parameter) diff --git a/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService.cs b/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService.cs index 9bcdcc876919e..23b62c110b1c7 100644 --- a/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService.cs +++ b/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService.cs @@ -57,7 +57,7 @@ public async ValueTask> GetInheritanceMemb var result = await remoteClient.TryInvokeAsync>( solution, (service, solutionInfo, cancellationToken) => - service.GetInheritanceMarginItemsAsync(solutionInfo, document.Id, spanToSearch, includeGlobalImports: includeGlobalImports, frozenPartialSemantics: frozenPartialSemantics, cancellationToken), + service.GetInheritanceMarginItemsAsync(solutionInfo, document.Id, spanToSearch, includeGlobalImports, frozenPartialSemantics, cancellationToken), cancellationToken).ConfigureAwait(false); if (!result.HasValue) @@ -72,8 +72,8 @@ public async ValueTask> GetInheritanceMemb return await GetInheritanceMarginItemsInProcessAsync( document, spanToSearch, - includeGlobalImports: includeGlobalImports, - frozenPartialSemantics: frozenPartialSemantics, + includeGlobalImports, + frozenPartialSemantics, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService_Helpers.cs b/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService_Helpers.cs index 6cd3cf5740f80..e81dc72b6dc5a 100644 --- a/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService_Helpers.cs +++ b/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService_Helpers.cs @@ -72,7 +72,7 @@ private static async ValueTask> GetSymbolI } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private async ValueTask<(Project remapped, SymbolAndLineNumberArray symbolAndLineNumbers)> GetMemberSymbolsAsync( @@ -133,7 +133,7 @@ private async Task> GetInheritanceMarginIt using var _ = ArrayBuilder.GetInstance(out var result); if (includeGlobalImports && !remapped) - result.AddRange(await GetGlobalImportsItemsAsync(document, spanToSearch, frozenPartialSemantics: frozenPartialSemantics, cancellationToken).ConfigureAwait(false)); + result.AddRange(await GetGlobalImportsItemsAsync(document, spanToSearch, frozenPartialSemantics, cancellationToken).ConfigureAwait(false)); if (!symbolAndLineNumbers.IsEmpty) { @@ -141,11 +141,11 @@ private async Task> GetInheritanceMarginIt remappedProject, document: remapped ? null : document, symbolAndLineNumbers, - frozenPartialSemantics: frozenPartialSemantics, + frozenPartialSemantics, cancellationToken).ConfigureAwait(false)); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private async Task> GetGlobalImportsItemsAsync( @@ -269,7 +269,7 @@ private async Task> GetGlobalImportsItemsA } } - return items.ToImmutable(); + return items.ToImmutableAndClear(); } private static async ValueTask AddInheritanceMemberItemsForNamedTypeAsync( @@ -756,6 +756,6 @@ private static ImmutableArray GetNonNullTargetItems(Immut } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs b/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs index e5fe8ea9ea5f1..4e17a45b3c04e 100644 --- a/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs +++ b/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs @@ -71,5 +71,5 @@ public bool Equals(InheritanceMarginItem other) => targetItems.IsEmpty ? null : new(lineNumber, topLevelDisplayText, displayTexts, glyph, Order(targetItems)); public static ImmutableArray Order(ImmutableArray targetItems) - => targetItems.OrderBy(t => t.DisplayName).ThenByDescending(t => t.LanguageGlyph).ThenBy(t => t.ProjectName ?? "").ToImmutableArray(); + => [.. targetItems.OrderBy(t => t.DisplayName).ThenByDescending(t => t.LanguageGlyph).ThenBy(t => t.ProjectName ?? "")]; } diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs b/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs index 1fd5cb23f3215..f806c61306e53 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs @@ -155,7 +155,7 @@ private async Task> HandleNoExistingFieldOrPropertyAs } } - return allActions.ToImmutable(); + return allActions.ToImmutableAndClear(); } private (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions( @@ -239,7 +239,7 @@ private static ImmutableArray GetParametersWithoutAssociatedMe result.Add(parameter); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private ImmutableArray HandleExistingFieldOrProperty( diff --git a/src/Features/Core/Portable/InitializeParameter/InitializeParameterHelpersCore.cs b/src/Features/Core/Portable/InitializeParameter/InitializeParameterHelpersCore.cs index bdf337e0b4399..2621c07da0bfb 100644 --- a/src/Features/Core/Portable/InitializeParameter/InitializeParameterHelpersCore.cs +++ b/src/Features/Core/Portable/InitializeParameter/InitializeParameterHelpersCore.cs @@ -31,7 +31,7 @@ internal static class InitializeParameterHelpersCore siblings.Add((method.Parameters[i], before: false)); } - return siblings.ToImmutable(); + return siblings.ToImmutableAndClear(); } public static bool IsParameterReference(IOperation? operation, IParameterSymbol parameter) diff --git a/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs b/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs index 60f4bda8a6118..fbcb5d6321723 100644 --- a/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs +++ b/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs @@ -77,7 +77,7 @@ public async Task> GetInlineHintsAsync( } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); void AddHintsIfAppropriate(SyntaxNode node) { diff --git a/src/Features/Core/Portable/InlineHints/AbstractInlineTypeHintsService.cs b/src/Features/Core/Portable/InlineHints/AbstractInlineTypeHintsService.cs index bea7757ce632c..6f96cb42b46e4 100644 --- a/src/Features/Core/Portable/InlineHints/AbstractInlineTypeHintsService.cs +++ b/src/Features/Core/Portable/InlineHints/AbstractInlineTypeHintsService.cs @@ -86,7 +86,7 @@ public async Task> GetInlineHintsAsync( InlineHintHelpers.GetDescriptionFunction(span.Start, type.GetSymbolKey(cancellationToken: cancellationToken), displayOptions))); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static void AddParts( diff --git a/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs b/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs index 1848fe2b0847d..3f7c1c32c7e43 100644 --- a/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs +++ b/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs @@ -54,7 +54,7 @@ private static async Task> GetDescriptionAsync(Docume } } - return parts.ToImmutableArray(); + return [.. parts]; } return default; diff --git a/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs b/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs index efadd9d050f2d..0d890aadf1f0b 100644 --- a/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs +++ b/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs @@ -395,7 +395,7 @@ public static ImmutableHashSet GetAllSymbols( var operation = semanticModel.GetOperation(methodDeclarationSyntax, cancellationToken); visitor.Visit(operation); - return visitor._allSymbols.ToImmutableHashSet(); + return [.. visitor._allSymbols]; } public override void Visit(IOperation? operation) diff --git a/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.MethodParametersInfo.cs b/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.MethodParametersInfo.cs index 40c4c9d5b0db0..cf2beec68451b 100644 --- a/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.MethodParametersInfo.cs +++ b/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.MethodParametersInfo.cs @@ -456,7 +456,7 @@ private static async Task> GetArgumentsReadOn } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } /// diff --git a/src/Features/Core/Portable/IntroduceParameter/IntroduceParameterDocumentRewriter.cs b/src/Features/Core/Portable/IntroduceParameter/IntroduceParameterDocumentRewriter.cs index e238ff6f75729..5ac5ef14103ec 100644 --- a/src/Features/Core/Portable/IntroduceParameter/IntroduceParameterDocumentRewriter.cs +++ b/src/Features/Core/Portable/IntroduceParameter/IntroduceParameterDocumentRewriter.cs @@ -668,9 +668,7 @@ private async Task UpdateExpressionInOriginalFunctionAsync(SyntaxEditor editor, private async Task> FindMatchesAsync(CancellationToken cancellationToken) { if (!_allOccurrences) - { - return SpecializedCollections.SingletonEnumerable(_expression); - } + return [_expression]; var syntaxFacts = _originalDocument.GetRequiredLanguageService(); var originalSemanticModel = await _originalDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs b/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs index 4a8b3713e4066..c63a0565e4fcd 100644 --- a/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs @@ -419,7 +419,7 @@ private ImmutableArray GetSubsequentStatementRanges(TIfStatement innerStatement = (TStatementSyntax)node; } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private SyntaxNode GetRootWithInvertIfStatement( @@ -524,8 +524,7 @@ private SyntaxNode GetRootWithInvertIfStatement( text, ifNode: ifNode, condition: negatedExpression, - trueStatement: AsEmbeddedStatement( - SpecializedCollections.SingletonEnumerable(newIfBody), original: ifBody)); + trueStatement: AsEmbeddedStatement([newIfBody], original: ifBody)); var statementsBeforeIf = statements.Take(index); @@ -551,8 +550,7 @@ private SyntaxNode GetRootWithInvertIfStatement( text, ifNode: ifNode, condition: negatedExpression, - trueStatement: AsEmbeddedStatement( - SpecializedCollections.SingletonEnumerable(newIfBody), ifBody)); + trueStatement: AsEmbeddedStatement([newIfBody], ifBody)); var statementsBeforeIf = statements.Take(index); diff --git a/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs b/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs index a7e30137050ab..9f6b7df2c5b39 100644 --- a/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs +++ b/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs @@ -32,17 +32,14 @@ private ImmutableArray GetDelegateAnonymousTypeParts( SemanticModel semanticModel, int position) { - using var _ = ArrayBuilder.GetInstance(out var parts); - var invokeMethod = anonymousType.DelegateInvokeMethod ?? throw ExceptionUtilities.Unreachable(); - parts.Add(new SymbolDisplayPart(SymbolDisplayPartKind.Keyword, symbol: null, - SyntaxFactsService.GetText(SyntaxFactsService.SyntaxKinds.DelegateKeyword))); - parts.AddRange(Space()); - parts.AddRange(MassageDelegateParts(invokeMethod, invokeMethod.ToMinimalDisplayParts( - semanticModel, position, s_delegateDisplay))); - - return parts.ToImmutable(); + return + [ + new SymbolDisplayPart(SymbolDisplayPartKind.Keyword, symbol: null, SyntaxFactsService.GetText(SyntaxFactsService.SyntaxKinds.DelegateKeyword)), + .. Space(), + .. MassageDelegateParts(invokeMethod, invokeMethod.ToMinimalDisplayParts(semanticModel, position, s_delegateDisplay)) + ]; } private static ImmutableArray MassageDelegateParts( @@ -58,7 +55,7 @@ private static ImmutableArray MassageDelegateParts( result.Add(part); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public StructuralTypeDisplayInfo GetTypeDisplayInfo( @@ -151,7 +148,7 @@ private static ImmutableArray OrderStructuralTypes( { if (symbol is IMethodSymbol method) { - return structuralTypes.OrderBy( + return [.. structuralTypes.OrderBy( (n1, n2) => { var index1 = method.TypeArguments.IndexOf(n1); @@ -160,11 +157,11 @@ private static ImmutableArray OrderStructuralTypes( index2 = index2 < 0 ? int.MaxValue : index2; return index1 - index2; - }).ToImmutableArray(); + })]; } else if (symbol is IPropertySymbol property) { - return structuralTypes.OrderBy( + return [.. structuralTypes.OrderBy( (n1, n2) => { if (n1.Equals(property.ContainingType) && !n2.Equals(property.ContainingType)) @@ -179,7 +176,7 @@ private static ImmutableArray OrderStructuralTypes( { return 0; } - }).ToImmutableArray(); + })]; } return structuralTypes; @@ -208,7 +205,7 @@ private static ImmutableArray GetTransitiveStructuralTypeRefer result.Add(namedType); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } protected static IEnumerable LineBreak(int count = 1) diff --git a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs index af5c892af388c..766fc86ca630a 100644 --- a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs +++ b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs @@ -771,10 +771,13 @@ protected void AddToGroup(SymbolDescriptionGroups group, params IEnumerable Description(string description) { - return Punctuation("(") - .Concat(PlainText(description)) - .Concat(Punctuation(")")) - .Concat(Space()); + return + [ + .. Punctuation("("), + .. PlainText(description), + .. Punctuation(")"), + .. Space(), + ]; } protected static IEnumerable Keyword(string text) diff --git a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/ISymbolDisplayService.cs b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/ISymbolDisplayService.cs index f9b338dd8784b..44eea00d4f175 100644 --- a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/ISymbolDisplayService.cs +++ b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/ISymbolDisplayService.cs @@ -8,7 +8,6 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis.LanguageService; diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.DocCommentFormatter.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.DocCommentFormatter.cs index b3208168182ff..c8181bea8e72d 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.DocCommentFormatter.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.DocCommentFormatter.cs @@ -140,7 +140,7 @@ internal static ImmutableArray Format(IDocumentationCommentFormattingSer while (formattedCommentLinesBuilder is [.., { Length: 0 }]) formattedCommentLinesBuilder.RemoveAt(formattedCommentLinesBuilder.Count - 1); - return formattedCommentLinesBuilder.ToImmutable(); + return formattedCommentLinesBuilder.ToImmutableAndClear(); } private static void AddWrappedTextFromRawText( diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.cs index e9e1226a3f58b..874bd38c45b91 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.cs @@ -71,7 +71,7 @@ public async Task AddSourceToAsync( var formattedDoc = await Formatter.FormatAsync( docWithAssemblyInfo, - SpecializedCollections.SingletonEnumerable(node.FullSpan), + [node.FullSpan], options.CleanupOptions.FormattingOptions, GetFormattingRules(docWithAssemblyInfo), cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs index fe3febe0beac0..d0abbcdeb54b1 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs @@ -30,7 +30,7 @@ internal class MetadataAsSourceFileService([ImportMany] IEnumerable - private readonly ImmutableArray> _providers = ExtensionOrderer.Order(providers).ToImmutableArray(); + private readonly ImmutableArray> _providers = [.. ExtensionOrderer.Order(providers)]; /// /// Workspace created the first time we generate any metadata for any symbol. diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs index bd27fae1aa8bf..3101d90c3049b 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs @@ -37,7 +37,7 @@ public MetadataAsSourceGeneratedFileInfo(string rootPath, Workspace sourceWorksp ? sourceProject.ParseOptions : sourceProject.Solution.Services.GetLanguageServices(LanguageName).GetRequiredService().GetDefaultParseOptionsWithLatestLanguageVersion(); - this.References = sourceProject.MetadataReferences.ToImmutableArray(); + this.References = [.. sourceProject.MetadataReferences]; this.AssemblyIdentity = topLevelNamedType.ContainingAssembly.Identity; var extension = LanguageName == LanguageNames.CSharp ? ".cs" : ".vb"; diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index c5575614c4958..0d983d125edbc 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -50,9 +50,10 @@ + - - + + diff --git a/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersWithDialogCodeAction.cs b/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersWithDialogCodeAction.cs index 41acdd3bfcfc5..230248be794f6 100644 --- a/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersWithDialogCodeAction.cs +++ b/src/Features/Core/Portable/MoveStaticMembers/MoveStaticMembersWithDialogCodeAction.cs @@ -45,9 +45,7 @@ protected override async Task> ComputeOperation object options, IProgress progressTracker, CancellationToken cancellationToken) { if (options is not MoveStaticMembersOptions moveOptions || moveOptions.IsCancelled) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; // Find the original doc root var syntaxFacts = _document.GetRequiredLanguageService(); diff --git a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs index f5e3536f2b965..bf2831289f9b8 100644 --- a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs +++ b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs @@ -60,7 +60,7 @@ protected sealed override async Task> ComputeOp } } - return SpecializedCollections.EmptyEnumerable(); + return []; } private static ImmutableArray CreateRenameOperations(MoveToNamespaceResult moveToNamespaceResult) @@ -87,7 +87,7 @@ private static ImmutableArray CreateRenameOperations(MoveTo } } - return operations.ToImmutable(); + return operations.ToImmutableAndClear(); } public static AbstractMoveToNamespaceCodeAction Generate(IMoveToNamespaceService changeNamespaceService, MoveToNamespaceAnalysisResult analysisResult, CodeCleanupOptionsProvider cleanupOptions) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 3a0fd67c501d9..015ea7d4a04f7 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -14,9 +13,8 @@ using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PatternMatching; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Storage; using Roslyn.Utilities; @@ -63,17 +61,19 @@ public async Task SearchCachedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); Debug.Assert(priorityDocuments.All(d => projects.Contains(d.Project))); - var onItemFound = GetOnItemFoundCallback(solution, activeDocument, onResultFound, cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound, cancellationToken); var documentKeys = projects.SelectManyAsArray(p => p.Documents.Select(DocumentKey.ToDocumentKey)); var priorityDocumentKeys = priorityDocuments.SelectAsArray(DocumentKey.ToDocumentKey); @@ -81,10 +81,10 @@ public async Task SearchCachedDocumentsAsync( var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemFound, onProjectCompleted); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted); await client.TryInvokeAsync( (service, callbackId, cancellationToken) => - service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; @@ -92,7 +92,7 @@ await client.TryInvokeAsync( var storageService = solution.Services.GetPersistentStorageService(); await SearchCachedDocumentsInCurrentProcessAsync( - storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds, onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } public static async Task SearchCachedDocumentsInCurrentProcessAsync( @@ -101,16 +101,14 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( ImmutableArray priorityDocumentKeys, string searchPattern, IImmutableSet kinds, - Func onItemFound, + Func, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); // Quick abort if OOP is now fully loaded. if (!ShouldSearchCachedDocuments(out _, out _)) return; - // If the user created a dotted pattern then we'll grab the last part of the name var (patternName, patternContainer) = PatternMatcher.GetNameAndContainer(searchPattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); @@ -120,84 +118,50 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( using var _1 = GetPooledHashSet(priorityDocumentKeys, out var priorityDocumentKeysSet); - // Sort the groups into a high pri group (projects that contain a high-pri doc), and low pri groups (those - // that don't). - using var _2 = GetPooledHashSet(groups.Where(g => g.Any(priorityDocumentKeysSet.Contains)), out var highPriorityGroups); - using var _3 = GetPooledHashSet(groups.Where(g => !highPriorityGroups.Contains(g)), out var lowPriorityGroups); - - await ProcessProjectGroupsAsync(highPriorityGroups).ConfigureAwait(false); - await ProcessProjectGroupsAsync(lowPriorityGroups).ConfigureAwait(false); - + // Sort the groups into a high pri group (projects that contain a high-pri doc), and low pri groups (those that + // don't), and process in that order. + await PerformParallelSearchAsync( + Prioritize(groups, g => g.Any(priorityDocumentKeysSet.Contains)), + ProcessSingleProjectGroupAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; - async Task ProcessProjectGroupsAsync(HashSet> groups) + async ValueTask ProcessSingleProjectGroupAsync( + IGrouping group, + Action onItemFound) { - cancellationToken.ThrowIfCancellationRequested(); - using var _ = ArrayBuilder.GetInstance(out var tasks); - - foreach (var group in groups) - tasks.Add(ProcessProjectGroupAsync(group)); + if (cancellationToken.IsCancellationRequested) + return; - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - async Task ProcessProjectGroupAsync(IGrouping group) - { - cancellationToken.ThrowIfCancellationRequested(); - await Task.Yield().ConfigureAwait(false); var project = group.Key; - // Break the project into high-pri docs and low pri docs. - using var _1 = GetPooledHashSet(group.Where(priorityDocumentKeysSet.Contains), out var highPriDocs); - using var _2 = GetPooledHashSet(group.Where(d => !highPriDocs.Contains(d)), out var lowPriDocs); - - await SearchCachedDocumentsInCurrentProcessAsync( - storageService, patternName, patternContainer, declaredSymbolInfoKindsSet, - onItemFound, highPriDocs, cancellationToken).ConfigureAwait(false); - - await SearchCachedDocumentsInCurrentProcessAsync( - storageService, patternName, patternContainer, declaredSymbolInfoKindsSet, - onItemFound, lowPriDocs, cancellationToken).ConfigureAwait(false); + // Break the project into high-pri docs and low pri docs, and process in that order. + await RoslynParallel.ForEachAsync( + Prioritize(group, priorityDocumentKeysSet.Contains), + cancellationToken, + async (documentKey, cancellationToken) => + { + var index = await GetIndexAsync(storageService, documentKey, cancellationToken).ConfigureAwait(false); + if (index == null) + return; + + ProcessIndex( + documentKey, document: null, patternName, patternContainer, declaredSymbolInfoKindsSet, + index, linkedIndices: null, onItemFound, cancellationToken); + }).ConfigureAwait(false); // done with project. Let the host know. await onProjectCompleted().ConfigureAwait(false); } } - private static async Task SearchCachedDocumentsInCurrentProcessAsync( - IChecksummedPersistentStorageService storageService, - string patternName, - string? patternContainer, - DeclaredSymbolInfoKindSet kinds, - Func onItemFound, - HashSet documentKeys, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - using var _ = ArrayBuilder.GetInstance(out var tasks); - - foreach (var documentKey in documentKeys) - { - tasks.Add(Task.Run(async () => - { - var index = await GetIndexAsync(storageService, documentKey, cancellationToken).ConfigureAwait(false); - if (index == null) - return; - - await ProcessIndexAsync( - documentKey, document: null, patternName, patternContainer, kinds, onItemFound, index, cancellationToken).ConfigureAwait(false); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - private static Task GetIndexAsync( IChecksummedPersistentStorageService storageService, DocumentKey documentKey, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return SpecializedTasks.Null(); + // Retrieve the string table we use to dedupe strings. If we can't get it, that means the solution has // fully loaded and we've switched over to normal navto lookup. if (!ShouldSearchCachedDocuments(out var cachedIndexMap, out var stringTable)) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 5037c87b9bbc0..370fa6fc8fb71 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -21,20 +22,22 @@ public async Task SearchGeneratedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); - var onItemFound = GetOnItemFoundCallback(solution, activeDocument, onResultFound, cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound, cancellationToken); var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemFound, onProjectCompleted); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted); await client.TryInvokeAsync( // Sync and search the full solution snapshot. While this function is called serially per project, @@ -43,40 +46,41 @@ await client.TryInvokeAsync( // compilations would not be shared and we'd have to rebuild them. solution, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchGeneratedDocumentsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchGeneratedDocumentsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; } await SearchGeneratedDocumentsInCurrentProcessAsync( - projects, searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + projects, searchPattern, kinds, onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( ImmutableArray projects, string pattern, IImmutableSet kinds, - Func onItemFound, + Func, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - // If the user created a dotted pattern then we'll grab the last part of the name var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(pattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - // Projects is already sorted in dependency order by the host. Process in that order so that prior - // compilations are available for later projects when needed. - foreach (var project in projects) + await PerformParallelSearchAsync(projects, ProcessSingleProjectAsync, onItemsFound, cancellationToken).ConfigureAwait(false); + return; + + async ValueTask ProcessSingleProjectAsync( + Project project, Action onItemFound) { - cancellationToken.ThrowIfCancellationRequested(); // First generate all the source-gen docs. Then handoff to the standard search routine to find matches in them. var sourceGeneratedDocs = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - using var _ = GetPooledHashSet(sourceGeneratedDocs, out var documents); - await ProcessDocumentsAsync( - searchDocument: null, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, documents, cancellationToken).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + sourceGeneratedDocs, + cancellationToken, + (document, cancellationToken) => SearchSingleDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); await onProjectCompleted().ConfigureAwait(false); } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 730f4cac07699..64a90f344cdfd 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -37,93 +37,48 @@ internal abstract partial class AbstractNavigateToSearchService (PatternMatchKind.LowercaseSubstring, NavigateToMatchKind.Fuzzy), ]; - private static async Task SearchProjectInCurrentProcessAsync( - Project project, ImmutableArray priorityDocuments, - Document? searchDocument, string pattern, IImmutableSet kinds, - Func onResultFound, - Func onProjectCompleted, - CancellationToken cancellationToken) - { - try - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - - // We're doing a real search over the fully loaded solution now. No need to hold onto the cached map - // of potentially stale indices. - ClearCachedData(); - - // If the user created a dotted pattern then we'll grab the last part of the name - var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(pattern); - - var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - - // Prioritize the active documents if we have any. - using var _1 = GetPooledHashSet(priorityDocuments.Where(d => project.ContainsDocument(d.Id)), out var highPriDocs); - using var _2 = GetPooledHashSet(project.Documents.Where(d => !highPriDocs.Contains(d)), out var lowPriDocs); - - await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onResultFound, highPriDocs, cancellationToken).ConfigureAwait(false); - - // Then process non-priority documents. - await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onResultFound, lowPriDocs, cancellationToken).ConfigureAwait(false); - } - finally - { - await onProjectCompleted().ConfigureAwait(false); - } - } - - private static async Task ProcessDocumentsAsync( - Document? searchDocument, + private static async ValueTask SearchSingleDocumentAsync( + Document document, string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func onItemFound, - HashSet documents, + Action onItemFound, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - using var _ = ArrayBuilder.GetInstance(out var tasks); + if (cancellationToken.IsCancellationRequested) + return; - foreach (var document in documents) - { - if (searchDocument != null && searchDocument != document) - continue; + // Get the index for the file we're searching, as well as for its linked siblings. We'll use the latter to add + // the information to a symbol about all the project TFMs is can be found in. + var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); + using var _ = ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>.GetInstance(out var linkedIndices); - cancellationToken.ThrowIfCancellationRequested(); - tasks.Add(ProcessDocumentAsync(document, patternName, patternContainer, kinds, onItemFound, cancellationToken)); + foreach (var linkedDocumentId in document.GetLinkedDocumentIds()) + { + var linkedDocument = document.Project.Solution.GetRequiredDocument(linkedDocumentId); + var linkedIndex = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(linkedDocument, cancellationToken).ConfigureAwait(false); + linkedIndices.Add((linkedIndex, linkedDocumentId.ProjectId)); } - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - private static async Task ProcessDocumentAsync( - Document document, - string patternName, - string? patternContainer, - DeclaredSymbolInfoKindSet kinds, - Func onResultFound, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - await Task.Yield().ConfigureAwait(false); - var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); - - await ProcessIndexAsync( - DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, onResultFound, index, cancellationToken).ConfigureAwait(false); + ProcessIndex( + DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, + index, linkedIndices, onItemFound, cancellationToken); } - private static async Task ProcessIndexAsync( + private static void ProcessIndex( DocumentKey documentKey, Document? document, string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func onResultFound, TopLevelSyntaxTreeIndex index, + ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>? linkedIndices, + Action onItemFound, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + var containerMatcher = patternContainer != null ? PatternMatcher.CreateDotSeparatedContainerMatcher(patternContainer, includeMatchedSpans: true) : null; @@ -133,50 +88,38 @@ private static async Task ProcessIndexAsync( foreach (var declaredSymbolInfo in index.DeclaredSymbolInfos) { + if (cancellationToken.IsCancellationRequested) + return; + // Namespaces are never returned in nav-to as they're too common and have too many locations. if (declaredSymbolInfo.Kind == DeclaredSymbolInfoKind.Namespace) continue; - await AddResultIfMatchAsync( - documentKey, document, - declaredSymbolInfo, - nameMatcher, containerMatcher, - kinds, onResultFound, cancellationToken).ConfigureAwait(false); - } - } + using var nameMatches = TemporaryArray.Empty; + using var containerMatches = TemporaryArray.Empty; - private static async Task AddResultIfMatchAsync( - DocumentKey documentKey, - Document? document, - DeclaredSymbolInfo declaredSymbolInfo, - PatternMatcher nameMatcher, - PatternMatcher? containerMatcher, - DeclaredSymbolInfoKindSet kinds, - Func onResultFound, - CancellationToken cancellationToken) - { - using var nameMatches = TemporaryArray.Empty; - using var containerMatches = TemporaryArray.Empty; - - cancellationToken.ThrowIfCancellationRequested(); - if (kinds.Contains(declaredSymbolInfo.Kind) && - nameMatcher.AddMatches(declaredSymbolInfo.Name, ref nameMatches.AsRef()) && - containerMatcher?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, ref containerMatches.AsRef()) != 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( - documentKey, document, declaredSymbolInfo, nameMatches, containerMatches, additionalMatchingProjects); - await onResultFound(result).ConfigureAwait(false); + if (kinds.Contains(declaredSymbolInfo.Kind) && + nameMatcher.AddMatches(declaredSymbolInfo.Name, ref nameMatches.AsRef()) && + containerMatcher?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, ref containerMatches.AsRef()) != false) + { + if (cancellationToken.IsCancellationRequested) + return; + + // 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 = GetAdditionalProjectsWithMatch( + document, declaredSymbolInfo, linkedIndices); + + var result = ConvertResult( + documentKey, document, declaredSymbolInfo, nameMatches, containerMatches, additionalMatchingProjects); + onItemFound(result); + } } } @@ -217,29 +160,25 @@ private static RoslynNavigateToItem ConvertResult( allPatternMatches.ToImmutableAndClear()); } - private static async ValueTask> GetAdditionalProjectsWithMatchAsync( - Document? document, DeclaredSymbolInfo declaredSymbolInfo, CancellationToken cancellationToken) + private static ImmutableArray GetAdditionalProjectsWithMatch( + Document? document, + DeclaredSymbolInfo declaredSymbolInfo, + ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>? linkedIndices) { - if (document == null) + if (document == null || linkedIndices is null || linkedIndices.Count == 0) return []; - using var _ = ArrayBuilder.GetInstance(out var result); + using var result = TemporaryArray.Empty; - var solution = document.Project.Solution; - var linkedDocumentIds = document.GetLinkedDocumentIds(); - foreach (var linkedDocumentId in linkedDocumentIds) + foreach (var (index, projectId) in linkedIndices) { - var linkedDocument = solution.GetRequiredDocument(linkedDocumentId); - var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(linkedDocument, cancellationToken).ConfigureAwait(false); - // See if the index for the other file also contains this same info. If so, merge the results so the // user only sees them as a single hit in the UI. if (index.DeclaredSymbolInfoSet.Contains(declaredSymbolInfo)) - result.Add(linkedDocument.Project.Id); + result.Add(projectId); } - result.RemoveDuplicates(); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static string GetItemKind(DeclaredSymbolInfo declaredSymbolInfo) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index cfc2f549f72ba..33f95b3cf70a7 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -3,14 +3,14 @@ // 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 System.Threading.Tasks; -using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -21,34 +21,41 @@ public async Task SearchDocumentAsync( Document document, string searchPattern, IImmutableSet kinds, - Func onResultFound, + Func, Task> onResultsFound, CancellationToken cancellationToken) { var solution = document.Project.Solution; - var onItemFound = GetOnItemFoundCallback(solution, activeDocument: null, (_, i) => onResultFound(i), cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument: null, onResultsFound, cancellationToken); var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemFound, onProjectCompleted: null); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted: null); // Don't need to sync the full solution when searching a single document. Just sync the project that doc is in. await client.TryInvokeAsync( document.Project, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchDocumentAsync(solutionInfo, document.Id, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchDocumentAsync(solutionInfo, document.Id, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; } - await SearchDocumentInCurrentProcessAsync(document, searchPattern, kinds, onItemFound, cancellationToken).ConfigureAwait(false); + await SearchDocumentInCurrentProcessAsync(document, searchPattern, kinds, onItemsFound, cancellationToken).ConfigureAwait(false); } - public static Task SearchDocumentInCurrentProcessAsync(Document document, string searchPattern, IImmutableSet kinds, Func onItemFound, CancellationToken cancellationToken) + public static async Task SearchDocumentInCurrentProcessAsync( + Document document, string searchPattern, IImmutableSet kinds, Func, Task> onItemsFound, CancellationToken cancellationToken) { - return SearchProjectInCurrentProcessAsync( - document.Project, priorityDocuments: [], document, searchPattern, kinds, - onItemFound, () => Task.CompletedTask, cancellationToken); + var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); + var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); + + var results = new ConcurrentSet(); + await SearchSingleDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, t => results.Add(t), cancellationToken).ConfigureAwait(false); + + if (results.Count > 0) + await onItemsFound(results.ToImmutableArray()).ConfigureAwait(false); } public async Task SearchProjectsAsync( @@ -58,22 +65,24 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); Debug.Assert(priorityDocuments.All(d => projects.Contains(d.Project))); - var onItemFound = GetOnItemFoundCallback(solution, activeDocument, onResultFound, cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound, cancellationToken); var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { var priorityDocumentIds = priorityDocuments.SelectAsArray(d => d.Id); - var callback = new NavigateToSearchServiceCallback(onItemFound, onProjectCompleted); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted); await client.TryInvokeAsync( // Intentionally sync the full solution. When SearchProjectAsync is called, we're searching all @@ -81,14 +90,14 @@ await client.TryInvokeAsync( // on the oop side. solution, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchProjectsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), priorityDocumentIds, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchProjectsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), priorityDocumentIds, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; } await SearchProjectsInCurrentProcessAsync( - projects, priorityDocuments, searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + projects, priorityDocuments, searchPattern, kinds, onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } public static async Task SearchProjectsInCurrentProcessAsync( @@ -96,34 +105,39 @@ public static async Task SearchProjectsInCurrentProcessAsync( ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, - Func onItemFound, + Func, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - using var _1 = GetPooledHashSet(priorityDocuments.Select(d => d.Project), out var highPriProjects); - using var _2 = GetPooledHashSet(projects.Where(p => !highPriProjects.Contains(p)), out var lowPriProjects); + // We're doing a real search over the fully loaded solution now. No need to hold onto the cached map + // of potentially stale indices. + ClearCachedData(); - Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); + var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); + var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - await ProcessProjectsAsync(highPriProjects).ConfigureAwait(false); - await ProcessProjectsAsync(lowPriProjects).ConfigureAwait(false); + using var _ = GetPooledHashSet(priorityDocuments.Select(d => d.Project), out var highPriProjects); + // Process each project on its own. That way we can tell the client when we are done searching it. Put the + // projects with priority documents ahead of those without so we can get results for those faster. + await PerformParallelSearchAsync( + Prioritize(projects, highPriProjects.Contains), + SearchSingleProjectAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; - async Task ProcessProjectsAsync(HashSet projects) + async ValueTask SearchSingleProjectAsync( + Project project, + Action onItemFound) { - using var _ = ArrayBuilder.GetInstance(out var tasks); + using var _ = GetPooledHashSet(priorityDocuments.Where(d => project == d.Project), out var highPriDocs); - foreach (var project in projects) - { - cancellationToken.ThrowIfCancellationRequested(); - tasks.Add(SearchProjectInCurrentProcessAsync( - project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, - searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)); - } + await RoslynParallel.ForEachAsync( + Prioritize(project.Documents, highPriDocs.Contains), + cancellationToken, + (document, cancellationToken) => SearchSingleDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); - await Task.WhenAll(tasks).ConfigureAwait(false); + await onProjectCompleted().ConfigureAwait(false); } } } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index ddb6e6066701e..4117157b3655d 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -8,14 +8,14 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; internal abstract partial class AbstractNavigateToSearchService : IAdvancedNavigateToSearchService { - public IImmutableSet KindsProvided { get; } = ImmutableHashSet.Create( + public static readonly IImmutableSet AllKinds = [ NavigateToItemKind.Class, NavigateToItemKind.Constant, NavigateToItemKind.Delegate, @@ -27,23 +27,28 @@ internal abstract partial class AbstractNavigateToSearchService : IAdvancedNavig NavigateToItemKind.Method, NavigateToItemKind.Module, NavigateToItemKind.Property, - NavigateToItemKind.Structure); + NavigateToItemKind.Structure]; + + public IImmutableSet KindsProvided { get; } = AllKinds; public bool CanFilter => true; - private static Func GetOnItemFoundCallback( - Solution solution, Document? activeDocument, Func onResultFound, CancellationToken cancellationToken) + private static Func, Task> GetOnItemsFoundCallback( + Solution solution, Document? activeDocument, Func, Task> onResultsFound, CancellationToken cancellationToken) { - return async item => + return async items => { - // This must succeed. We should always be searching for items that correspond to documents/projects in - // the host side solution. Note: this even includes 'cached' items. While those may correspond to - // stale versions of a document, it should still be for documents that the host has asked about. - var project = solution.GetRequiredProject(item.DocumentId.ProjectId); - - var result = await item.TryCreateSearchResultAsync(solution, activeDocument, cancellationToken).ConfigureAwait(false); - if (result != null) - await onResultFound(project, result).ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(items.Length, out var results); + + foreach (var item in items) + { + var result = await item.TryCreateSearchResultAsync(solution, activeDocument, cancellationToken).ConfigureAwait(false); + if (result != null) + results.Add(result); + } + + if (results.Count > 0) + await onResultsFound(results.ToImmutableAndClear()).ConfigureAwait(false); }; } @@ -60,4 +65,38 @@ private static PooledDisposer> GetPooledHashSet(ImmutableArr instance.AddRange(items); return disposer; } + + private static IEnumerable Prioritize(IEnumerable items, Func isPriority) + { + using var _ = ArrayBuilder.GetInstance(out var normalItems); + + foreach (var item in items) + { + if (isPriority(item)) + yield return item; + else + normalItems.Add(item); + } + + foreach (var item in normalItems) + yield return item; + } + + /// + /// Main utility for searching across items in a solution. The actual code to search the item should be provided in + /// . Each item in will be processed using + /// Parallel.ForEachAsync, allowing for parallel processing of the items, with a preference towards + /// earlier items. + /// + private static Task PerformParallelSearchAsync( + IEnumerable items, + Func, ValueTask> callback, + Func, Task> onItemsFound, + CancellationToken cancellationToken) + => ProducerConsumer.RunAsync( + ProducerConsumerOptions.SingleReaderOptions, + produceItems: static (onItemFound, args) => RoslynParallel.ForEachAsync(args.items, args.cancellationToken, (item, cancellationToken) => args.callback(item, onItemFound)), + consumeItems: static (items, args) => args.onItemsFound(items), + args: (items, callback, onItemsFound, cancellationToken), + cancellationToken); } diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs index 281c9ffc89b56..4a2e95b4e31ba 100644 --- a/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs +++ b/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.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.Immutable; using System.Threading; using System.Threading.Tasks; @@ -12,7 +13,7 @@ internal interface INavigateToSearchCallback void Done(bool isFullyLoaded); void ReportIncomplete(); - Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken); + Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken); void ReportProgress(int current, int maximum); } diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs index 761a370778a72..ded2ae3bca7f4 100644 --- a/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs +++ b/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs @@ -3,10 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.PatternMatching; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs index 70365273bff52..2afcd69e24ece 100644 --- a/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs @@ -19,7 +19,7 @@ Task SearchDocumentAsync( Document document, string searchPattern, IImmutableSet kinds, - Func onResultFound, + Func, Task> onResultsFound, CancellationToken cancellationToken); /// @@ -39,7 +39,7 @@ Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken); } @@ -66,7 +66,7 @@ Task SearchCachedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken); @@ -84,7 +84,7 @@ Task SearchGeneratedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken); } diff --git a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs index 2f9811f0801b5..059b03a171a78 100644 --- a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs @@ -26,7 +26,7 @@ internal interface IRemoteNavigateToSearchService public interface ICallback { - ValueTask OnResultFoundAsync(RemoteServiceCallbackId callbackId, RoslynNavigateToItem result); + ValueTask OnItemsFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray items); ValueTask OnProjectCompletedAsync(RemoteServiceCallbackId callbackId); } } @@ -39,22 +39,22 @@ internal sealed class NavigateToSearchServiceServerCallbackDispatcher() : Remote private new NavigateToSearchServiceCallback GetCallback(RemoteServiceCallbackId callbackId) => (NavigateToSearchServiceCallback)base.GetCallback(callbackId); - public ValueTask OnResultFoundAsync(RemoteServiceCallbackId callbackId, RoslynNavigateToItem result) - => GetCallback(callbackId).OnResultFoundAsync(result); + public ValueTask OnItemsFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray items) + => GetCallback(callbackId).OnItemsFoundAsync(items); public ValueTask OnProjectCompletedAsync(RemoteServiceCallbackId callbackId) => GetCallback(callbackId).OnProjectCompletedAsync(); } internal sealed class NavigateToSearchServiceCallback( - Func onResultFound, + Func, Task> onItemsFound, Func? onProjectCompleted) { - public async ValueTask OnResultFoundAsync(RoslynNavigateToItem result) + public async ValueTask OnItemsFoundAsync(ImmutableArray items) { try { - await onResultFound(result).ConfigureAwait(false); + await onItemsFound(items).ConfigureAwait(false); } catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex)) { diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs index fef9c78daf33c..21df05a617e82 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -22,21 +22,30 @@ namespace Microsoft.CodeAnalysis.NavigateTo; [Flags] -internal enum NavigateToSearchScope +internal enum NavigateToDocumentSupport { RegularDocuments = 0b01, GeneratedDocuments = 0b10, AllDocuments = RegularDocuments | GeneratedDocuments } +internal enum NavigateToSearchScope +{ + // Intentionally first so that no value indicates searching the entire solution. + Solution, + Project, + Document, +} + internal sealed class NavigateToSearcher { + private static readonly ObjectPool> s_searchResultPool = new(() => new(NavigateToSearchResultComparer.Instance)); + private readonly INavigateToSearcherHost _host; private readonly Solution _solution; private readonly INavigateToSearchCallback _callback; private readonly string _searchPattern; private readonly IImmutableSet _kinds; - private readonly IAsynchronousOperationListener _listener; private readonly IStreamingProgressTracker _progress_doNotAccessDirectly; private readonly Document? _activeDocument; @@ -49,15 +58,13 @@ private NavigateToSearcher( Solution solution, INavigateToSearchCallback callback, string searchPattern, - IImmutableSet kinds, - IAsynchronousOperationListener listener) + IImmutableSet kinds) { _host = host; _solution = solution; _callback = callback; _searchPattern = searchPattern; _kinds = kinds; - _listener = listener; _progress_doNotAccessDirectly = new StreamingProgressTracker((current, maximum, ct) => { callback.ReportProgress(current, maximum); @@ -91,18 +98,17 @@ public static NavigateToSearcher Create( CancellationToken disposalToken) { var host = new DefaultNavigateToSearchHost(solution, asyncListener, disposalToken); - return Create(solution, asyncListener, callback, searchPattern, kinds, host); + return Create(solution, callback, searchPattern, kinds, host); } public static NavigateToSearcher Create( Solution solution, - IAsynchronousOperationListener asyncListener, INavigateToSearchCallback callback, string searchPattern, IImmutableSet kinds, INavigateToSearcherHost host) { - return new NavigateToSearcher(host, solution, callback, searchPattern, kinds, asyncListener); + return new NavigateToSearcher(host, solution, callback, searchPattern, kinds); } private async Task AddProgressItemsAsync(int count, CancellationToken cancellationToken) @@ -122,12 +128,12 @@ private async Task ProgressItemsCompletedAsync(int count, CancellationToken canc await _progress_doNotAccessDirectly.ItemsCompletedAsync(count, cancellationToken).ConfigureAwait(false); } - public Task SearchAsync(bool searchCurrentDocument, CancellationToken cancellationToken) - => SearchAsync(searchCurrentDocument, NavigateToSearchScope.AllDocuments, cancellationToken); + public Task SearchAsync(NavigateToSearchScope searchScope, CancellationToken cancellationToken) + => SearchAsync(searchScope, NavigateToDocumentSupport.AllDocuments, cancellationToken); public async Task SearchAsync( - bool searchCurrentDocument, - NavigateToSearchScope scope, + NavigateToSearchScope searchScope, + NavigateToDocumentSupport documentSupport, CancellationToken cancellationToken) { var isFullyLoaded = true; @@ -136,22 +142,31 @@ public async Task SearchAsync( { using var navigateToSearch = Logger.LogBlock(FunctionId.NavigateTo_Search, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken); - if (searchCurrentDocument) + switch (searchScope) { - await SearchCurrentDocumentAsync(cancellationToken).ConfigureAwait(false); - } - else - { - // We consider ourselves fully loaded when both the project system has completed loaded us, and we've - // totally hydrated the oop side. Until that happens, we'll attempt to return cached data from languages - // that support that. - isFullyLoaded = await _host.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false); + case NavigateToSearchScope.Document: + await SearchCurrentDocumentAsync(cancellationToken).ConfigureAwait(false); + return; + + case NavigateToSearchScope.Project: + await SearchCurrentProjectAsync(documentSupport, cancellationToken).ConfigureAwait(false); + return; + + case NavigateToSearchScope.Solution: + // We consider ourselves fully loaded when both the project system has completed loaded us, and we've + // totally hydrated the oop side. Until that happens, we'll attempt to return cached data from languages + // that support that. + isFullyLoaded = await _host.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false); - // Let the UI know if we're not fully loaded (and then might be reporting cached results). - if (!isFullyLoaded) - _callback.ReportIncomplete(); + // Let the UI know if we're not fully loaded (and then might be reporting cached results). + if (!isFullyLoaded) + _callback.ReportIncomplete(); - await SearchAllProjectsAsync(isFullyLoaded, scope, cancellationToken).ConfigureAwait(false); + await SearchAllProjectsAsync(isFullyLoaded, documentSupport, cancellationToken).ConfigureAwait(false); + return; + + default: + throw ExceptionUtilities.UnexpectedValue(searchScope); } } finally @@ -176,23 +191,50 @@ private async Task SearchCurrentDocumentAsync(CancellationToken cancellationToke await AddProgressItemsAsync(1, cancellationToken).ConfigureAwait(false); await service.SearchDocumentAsync( _activeDocument, _searchPattern, _kinds, - r => _callback.AddItemAsync(project, r, cancellationToken), + r => _callback.AddResultsAsync(r, cancellationToken), cancellationToken).ConfigureAwait(false); } + private Task SearchCurrentProjectAsync( + NavigateToDocumentSupport documentSupport, + CancellationToken cancellationToken) + { + if (_activeDocument == null) + return Task.CompletedTask; + + var activeProject = _activeDocument.Project; + return SearchSpecificProjectsAsync( + // Because we're only searching the current project, it's fine to bring that project fully up to date before + // searching it. We only do the work to search cached files when doing the initial load of something huge + // (the full solution). + isFullyLoaded: true, + documentSupport, + [[activeProject]], + cancellationToken); + } + private INavigateToSearchService GetNavigateToSearchService(Project project) => _host.GetNavigateToSearchService(project) ?? NoOpNavigateToSearchService.Instance; private async Task SearchAllProjectsAsync( bool isFullyLoaded, - NavigateToSearchScope scope, + NavigateToDocumentSupport documentSupport, CancellationToken cancellationToken) { - var seenItems = new HashSet(NavigateToSearchResultComparer.Instance); var orderedProjects = GetOrderedProjectsToProcess(); + await SearchSpecificProjectsAsync(isFullyLoaded, documentSupport, orderedProjects, cancellationToken).ConfigureAwait(false); + } + + private async Task SearchSpecificProjectsAsync( + bool isFullyLoaded, + NavigateToDocumentSupport documentSupport, + ImmutableArray> orderedProjects, + CancellationToken cancellationToken) + { + using var _1 = s_searchResultPool.GetPooledObject(out var seenItems); - var searchRegularDocuments = scope.HasFlag(NavigateToSearchScope.RegularDocuments); - var searchGeneratedDocuments = scope.HasFlag(NavigateToSearchScope.GeneratedDocuments); + var searchRegularDocuments = documentSupport.HasFlag(NavigateToDocumentSupport.RegularDocuments); + var searchGeneratedDocuments = documentSupport.HasFlag(NavigateToDocumentSupport.GeneratedDocuments); Debug.Assert(searchRegularDocuments || searchGeneratedDocuments); var projectCount = orderedProjects.Sum(g => g.Length); @@ -201,7 +243,7 @@ private async Task SearchAllProjectsAsync( { // We're potentially about to make many calls over to our OOP service to perform searches. Ensure the // solution we're searching stays pinned between us and it while this is happening. - using var _ = RemoteKeepAliveSession.Create(_solution, _listener); + using var _2 = await RemoteKeepAliveSession.CreateAsync(_solution, cancellationToken).ConfigureAwait(false); // We may do up to two passes. One for loaded docs. One for source generated docs. await AddProgressItemsAsync( @@ -212,7 +254,7 @@ await AddProgressItemsAsync( await SearchFullyLoadedProjectsAsync(orderedProjects, seenItems, cancellationToken).ConfigureAwait(false); if (searchGeneratedDocuments) - await SearchGeneratedDocumentsAsync(seenItems, cancellationToken).ConfigureAwait(false); + await SearchGeneratedDocumentsAsync(orderedProjects, seenItems, cancellationToken).ConfigureAwait(false); } else { @@ -297,14 +339,14 @@ private ImmutableArray GetPriorityDocuments(ImmutableArray pr } result.RemoveDuplicates(); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private async Task ProcessOrderedProjectsAsync( bool parallel, ImmutableArray> orderedProjects, - HashSet seenItems, - Func, Func, Func, Task> processProjectAsync, + HashSet seenResults, + Func, Func, Task>, Func, Task> processProjectAsync, CancellationToken cancellationToken) { // Process each group one at a time. However, in each group process all projects in parallel to get results @@ -339,19 +381,27 @@ async Task SearchCoreAsync(IGrouping grouping var searchService = grouping.Key; await processProjectAsync( searchService, - grouping.ToImmutableArray(), - (project, result) => + [.. grouping], + results => { + using var _ = ArrayBuilder.GetInstance(results.Length, out var nonDuplicates); + // If we're seeing a dupe in another project, then filter it out here. The results from // the individual projects will already contain the information about all the projects // leading to a better condensed view that doesn't look like it contains duplicate info. - lock (seenItems) + lock (seenResults) { - if (!seenItems.Add(result)) - return Task.CompletedTask; + foreach (var result in results) + { + if (seenResults.Add(result)) + nonDuplicates.Add(result); + } } - return _callback.AddItemAsync(project, result, cancellationToken); + if (nonDuplicates.Count > 0) + _callback.AddResultsAsync(nonDuplicates.ToImmutableAndClear(), cancellationToken); + + return Task.CompletedTask; }, () => this.ProgressItemsCompletedAsync(count: 1, cancellationToken)).ConfigureAwait(false); } @@ -388,7 +438,7 @@ private Task SearchCachedDocumentsAsync( parallel: true, orderedProjects, seenItems, - async (service, projects, onItemFound, onProjectCompleted) => + async (service, projects, onResultsFound, onProjectCompleted) => { // if the language doesn't support searching cached docs, immediately transition the project to the // completed state. @@ -401,16 +451,20 @@ private Task SearchCachedDocumentsAsync( { await advancedService.SearchCachedDocumentsAsync( _solution, projects, GetPriorityDocuments(projects), _searchPattern, _kinds, _activeDocument, - onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + onResultsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } }, cancellationToken); } private Task SearchGeneratedDocumentsAsync( + ImmutableArray> orderedProjects, HashSet seenItems, CancellationToken cancellationToken) { + using var _ = PooledHashSet.GetInstance(out var allProjectIdSet); + allProjectIdSet.AddRange(orderedProjects.SelectMany(x => x).Select(p => p.Id)); + // Process all projects, serially, in topological order. Generating source can be expensive. It requires // creating and processing the entire compilation for a project, which itself may require dependent // compilations as references. These dependents might also be skeleton references in the case of cross @@ -422,18 +476,26 @@ private Task SearchGeneratedDocumentsAsync( // the dependency tree, which then pulls on N other projects, forcing results for this single project to pay // that full price (that would be paid when we hit these through a normal topological walk). // - // Note the projects in each 'dependency set' are already sorted in topological order. So they will process - // in the desired order if we process serially. - var allProjects = _solution + // Note: the projects in each 'dependency set' are already sorted in topological order. So they will process in + // the desired order if we process serially. + // + // Note: we should only process the projects that are in the ordered-list of projects the searcher is searching + // as a whole. + var filteredProjects = _solution .GetProjectDependencyGraph() .GetDependencySets(cancellationToken) - .SelectAsArray(s => s.SelectAsArray(_solution.GetRequiredProject)); + .SelectAsArray(projectIdSet => + projectIdSet.Where(id => allProjectIdSet.Contains(id)) + .Select(id => _solution.GetRequiredProject(id)) + .ToImmutableArray()); + + Contract.ThrowIfFalse(orderedProjects.SelectMany(s => s).Count() == filteredProjects.SelectMany(s => s).Count()); return ProcessOrderedProjectsAsync( parallel: false, - allProjects, + filteredProjects, seenItems, - async (service, projects, onItemFound, onProjectCompleted) => + async (service, projects, onResultsFound, onProjectCompleted) => { // if the language doesn't support searching generated docs, immediately transition the project to the // completed state. @@ -445,7 +507,7 @@ private Task SearchGeneratedDocumentsAsync( else { await advancedService.SearchGeneratedDocumentsAsync( - _solution, projects, _searchPattern, _kinds, _activeDocument, onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + _solution, projects, _searchPattern, _kinds, _activeDocument, onResultsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } }, cancellationToken); @@ -465,10 +527,10 @@ public IImmutableSet KindsProvided public bool CanFilter => false; - public Task SearchDocumentAsync(Document document, string searchPattern, IImmutableSet kinds, Func onResultFound, CancellationToken cancellationToken) + public Task SearchDocumentAsync(Document document, string searchPattern, IImmutableSet kinds, Func, Task> onResultsFound, CancellationToken cancellationToken) => Task.CompletedTask; - public async Task SearchProjectsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + public async Task SearchProjectsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { foreach (var _ in projects) await onProjectCompleted().ConfigureAwait(false); diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index 7437657dc3fc5..325b107a6cf29 100644 --- a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs +++ b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs @@ -87,7 +87,7 @@ internal readonly struct RoslynNavigateToItem( } } - private class NavigateToSearchResult : INavigateToSearchResult, INavigableItem + private sealed class NavigateToSearchResult : INavigateToSearchResult, INavigableItem { private static readonly char[] s_dotArray = ['.']; diff --git a/src/Features/Core/Portable/Navigation/AbstractNavigableItemsService.cs b/src/Features/Core/Portable/Navigation/AbstractNavigableItemsService.cs index 8322ad19fadbd..11c66bdf46e6b 100644 --- a/src/Features/Core/Portable/Navigation/AbstractNavigableItemsService.cs +++ b/src/Features/Core/Portable/Navigation/AbstractNavigableItemsService.cs @@ -12,21 +12,41 @@ namespace Microsoft.CodeAnalysis.Navigation; -internal class AbstractNavigableItemsService : INavigableItemsService +internal abstract class AbstractNavigableItemsService : INavigableItemsService { public async Task> GetNavigableItemsAsync( Document document, int position, CancellationToken cancellationToken) { var symbolService = document.GetRequiredLanguageService(); - var (symbol, project, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync(document, position, cancellationToken).ConfigureAwait(false); - var solution = project.Solution; - symbol = await SymbolFinder.FindSourceDefinitionAsync(symbol, solution, cancellationToken).ConfigureAwait(false) ?? symbol; - symbol = await GoToDefinitionFeatureHelpers.TryGetPreferredSymbolAsync(solution, symbol, cancellationToken).ConfigureAwait(false); + // First try with frozen partial semantics. For the common case where no symbols referenced though skeleton + // references are involved, this can be much faster. If that fails, try again, this time allowing skeletons to + // be built. + var symbolAndSolution = + await GetSymbolAsync(document.WithFrozenPartialSemantics(cancellationToken)).ConfigureAwait(false) ?? + await GetSymbolAsync(document).ConfigureAwait(false); + + if (symbolAndSolution is null) + return []; + + var (symbol, solution) = symbolAndSolution.Value; // Try to compute source definitions from symbol. - return symbol != null - ? NavigableItemFactory.GetItemsFromPreferredSourceLocations(solution, symbol, displayTaggedParts: FindUsagesHelpers.GetDisplayParts(symbol), cancellationToken: cancellationToken) - : []; + return NavigableItemFactory.GetItemsFromPreferredSourceLocations(solution, symbol, FindUsagesHelpers.GetDisplayParts(symbol), cancellationToken); + + async Task<(ISymbol symbol, Solution solution)?> GetSymbolAsync(Document document) + { + var (symbol, project, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync(document, position, cancellationToken).ConfigureAwait(false); + + var solution = project.Solution; + + symbol = await SymbolFinder.FindSourceDefinitionAsync(symbol, solution, cancellationToken).ConfigureAwait(false) ?? symbol; + symbol = await GoToDefinitionFeatureHelpers.TryGetPreferredSymbolAsync(solution, symbol, cancellationToken).ConfigureAwait(false); + + if (symbol is null or IErrorTypeSymbol) + return null; + + return (symbol, solution); + } } } diff --git a/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs b/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs index dd6c5fc18a8c0..164341deca9a8 100644 --- a/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationService.cs @@ -2,14 +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. +using System; +using System.Composition; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Navigation; -internal sealed class DefaultDocumentNavigationService : IDocumentNavigationService +[ExportWorkspaceService(typeof(IDocumentNavigationService), ServiceLayer.Default), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DefaultDocumentNavigationService() : IDocumentNavigationService { public Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) => SpecializedTasks.False; diff --git a/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationServiceFactory.cs b/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationServiceFactory.cs deleted file mode 100644 index 78e8f43807751..0000000000000 --- a/src/Features/Core/Portable/Navigation/DefaultDocumentNavigationServiceFactory.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. - -#nullable disable - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.Navigation; - -[ExportWorkspaceServiceFactory(typeof(IDocumentNavigationService), ServiceLayer.Default), Shared] -internal sealed class DefaultDocumentNavigationServiceFactory : IWorkspaceServiceFactory -{ - private IDocumentNavigationService _singleton; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultDocumentNavigationServiceFactory() - { - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - { - _singleton ??= new DefaultDocumentNavigationService(); - - return _singleton; - } -} diff --git a/src/Features/Core/Portable/Organizing/Organizers/AbstractSyntaxNodeOrganizer.cs b/src/Features/Core/Portable/Organizing/Organizers/AbstractSyntaxNodeOrganizer.cs index 86151b89ca495..97d341e1bfb25 100644 --- a/src/Features/Core/Portable/Organizing/Organizers/AbstractSyntaxNodeOrganizer.cs +++ b/src/Features/Core/Portable/Organizing/Organizers/AbstractSyntaxNodeOrganizer.cs @@ -14,10 +14,7 @@ namespace Microsoft.CodeAnalysis.Organizing.Organizers; internal abstract class AbstractSyntaxNodeOrganizer : ISyntaxOrganizer where TSyntaxNode : SyntaxNode { - public IEnumerable SyntaxNodeTypes - { - get { return SpecializedCollections.SingletonEnumerable(typeof(TSyntaxNode)); } - } + public IEnumerable SyntaxNodeTypes => [typeof(TSyntaxNode)]; public SyntaxNode OrganizeNode(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) => Organize((TSyntaxNode)node, cancellationToken); diff --git a/src/Features/Core/Portable/PdbSourceDocument/DocumentDebugInfoReader.cs b/src/Features/Core/Portable/PdbSourceDocument/DocumentDebugInfoReader.cs index c6e7b17c593ca..154bd02308ad4 100644 --- a/src/Features/Core/Portable/PdbSourceDocument/DocumentDebugInfoReader.cs +++ b/src/Features/Core/Portable/PdbSourceDocument/DocumentDebugInfoReader.cs @@ -37,7 +37,7 @@ public ImmutableArray FindSourceDocuments(EntityHandle entityHan { var documentHandles = SymbolSourceDocumentFinder.FindDocumentHandles(entityHandle, _dllReader, _pdbReader); - using var _ = ArrayBuilder.GetInstance(out var sourceDocuments); + var sourceDocuments = new FixedSizeArrayBuilder(documentHandles.Count); foreach (var handle in documentHandles) { @@ -54,7 +54,7 @@ public ImmutableArray FindSourceDocuments(EntityHandle entityHan sourceDocuments.Add(new SourceDocument(filePath, hashAlgorithm, checksum, embeddedTextBytes, sourceLinkUrl)); } - return sourceDocuments.ToImmutable(); + return sourceDocuments.MoveToImmutable(); } private string? TryGetSourceLinkUrl(DocumentHandle handle) diff --git a/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs b/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs index 10e1a2dcfa4c3..240ad43429469 100644 --- a/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs +++ b/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs @@ -338,7 +338,7 @@ private ImmutableArray CreateDocumentInfos( _fileToDocumentInfoMap[info.FilePath] = new(documentId, encoding, info.ChecksumAlgorithm, sourceProject.Id, sourceWorkspace); } - return documents.ToImmutable(); + return documents.ToImmutableAndClear(); } private static void AssertIsMainThread(MetadataAsSourceWorkspace workspace) diff --git a/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs b/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs index 193ffd4030719..b036ced7dcf76 100644 --- a/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs +++ b/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs @@ -104,7 +104,7 @@ private static ImmutableArray FindAllValidDestinations( { var allDestinations = selectedMembers.All(m => m.IsKind(SymbolKind.Field)) ? containingType.GetBaseTypes().ToImmutableArray() - : containingType.AllInterfaces.Concat(containingType.GetBaseTypes()).ToImmutableArray(); + : [.. containingType.AllInterfaces, .. containingType.GetBaseTypes()]; return allDestinations.WhereAsArray(destination => MemberAndDestinationValidator.IsDestinationValid(solution, destination, cancellationToken)); } diff --git a/src/Features/Core/Portable/PullMemberUp/Dialog/PullMemberUpWithDialogCodeAction.cs b/src/Features/Core/Portable/PullMemberUp/Dialog/PullMemberUpWithDialogCodeAction.cs index ff4984a8d706b..8628c09a294b2 100644 --- a/src/Features/Core/Portable/PullMemberUp/Dialog/PullMemberUpWithDialogCodeAction.cs +++ b/src/Features/Core/Portable/PullMemberUp/Dialog/PullMemberUpWithDialogCodeAction.cs @@ -49,7 +49,7 @@ protected override async Task> ComputeOperation else { // If user click cancel button, options will be null and hit this branch - return SpecializedCollections.EmptyEnumerable(); + return []; } } } diff --git a/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs b/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs index 0ab3b58f2910a..7091430a3c1b0 100644 --- a/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs +++ b/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs @@ -489,7 +489,7 @@ private static async Task @ref.GetSyntaxAsync(cancellationToken)); var allSyntaxes = await Task.WhenAll(tasks).ConfigureAwait(false); - symbolToDeclarationsBuilder.Add(memberAnalysisResult.Member, allSyntaxes.ToImmutableArray()); + symbolToDeclarationsBuilder.Add(memberAnalysisResult.Member, [.. allSyntaxes]); } return symbolToDeclarationsBuilder.ToImmutableDictionary(); diff --git a/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs b/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs index d8e20f3fe9c71..3f28908e0ee83 100644 --- a/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs +++ b/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs @@ -29,8 +29,9 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf var cancellationToken = context.CancellationToken; var semanticModel = await context.Document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var services = context.Document.Project.Solution.Services; + var onTheFlyDocsElement = await GetOnTheFlyDocsElementAsync(context, cancellationToken).ConfigureAwait(false); return await CreateContentAsync( - services, semanticModel, token, tokenInformation, supportedPlatforms, context.Options, cancellationToken).ConfigureAwait(false); + services, semanticModel, token, tokenInformation, supportedPlatforms, context.Options, onTheFlyDocsElement, cancellationToken).ConfigureAwait(false); } protected override async Task BuildQuickInfoAsync( @@ -40,8 +41,9 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf if (tokenInformation.Symbols.IsDefaultOrEmpty) return null; + // onTheFlyDocElement is null here since On-The-Fly Docs are being computed at the document level. return await CreateContentAsync( - context.Services, context.SemanticModel, token, tokenInformation, supportedPlatforms: null, context.Options, context.CancellationToken).ConfigureAwait(false); + context.Services, context.SemanticModel, token, tokenInformation, supportedPlatforms: null, context.Options, onTheFlyDocsElement: null, context.CancellationToken).ConfigureAwait(false); } private async Task<(TokenInformation tokenInformation, SupportedPlatformData? supportedPlatforms)> ComputeQuickInfoDataAsync( @@ -155,6 +157,7 @@ protected static Task CreateContentAsync( TokenInformation tokenInformation, SupportedPlatformData? supportedPlatforms, SymbolDescriptionOptions options, + OnTheFlyDocsElement? onTheFlyDocsElement, CancellationToken cancellationToken) { var syntaxFactsService = services.GetRequiredLanguageService(semanticModel.Language); @@ -164,21 +167,24 @@ protected static Task CreateContentAsync( // if generating quick info for an attribute, prefer bind to the class instead of the constructor if (syntaxFactsService.IsAttributeName(token.Parent!)) { - symbols = symbols.OrderBy((s1, s2) => + symbols = [.. symbols.OrderBy((s1, s2) => s1.Kind == s2.Kind ? 0 : s1.Kind == SymbolKind.NamedType ? -1 : - s2.Kind == SymbolKind.NamedType ? 1 : 0).ToImmutableArray(); + s2.Kind == SymbolKind.NamedType ? 1 : 0)]; } return QuickInfoUtilities.CreateQuickInfoItemAsync( services, semanticModel, token.Span, symbols, supportedPlatforms, - tokenInformation.ShowAwaitReturn, tokenInformation.NullableFlowState, options, cancellationToken); + tokenInformation.ShowAwaitReturn, tokenInformation.NullableFlowState, options, onTheFlyDocsElement, cancellationToken); } protected abstract bool GetBindableNodeForTokenIndicatingLambda(SyntaxToken token, [NotNullWhen(returnValue: true)] out SyntaxNode? found); protected abstract bool GetBindableNodeForTokenIndicatingPossibleIndexerAccess(SyntaxToken token, [NotNullWhen(returnValue: true)] out SyntaxNode? found); protected abstract bool GetBindableNodeForTokenIndicatingMemberAccess(SyntaxToken token, out SyntaxToken found); + protected virtual Task GetOnTheFlyDocsElementAsync(QuickInfoContext context, CancellationToken cancellationToken) + => Task.FromResult(null); + protected virtual NullableFlowState GetNullabilityAnalysis(SemanticModel semanticModel, ISymbol symbol, SyntaxNode node, CancellationToken cancellationToken) => NullableFlowState.None; private TokenInformation BindToken( diff --git a/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs b/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs index 09fbb8c8c6fbe..9bbcd5aac43f3 100644 --- a/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs +++ b/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs @@ -73,7 +73,7 @@ public static ImmutableArray GetSpansWithAlignedIndentation( } } - return adjustedClassifiedSpans.ToImmutableArray(); + return [.. adjustedClassifiedSpans]; } else { diff --git a/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsElement.cs b/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsElement.cs new file mode 100644 index 0000000000000..50ca692b162d2 --- /dev/null +++ b/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsElement.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.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.QuickInfo; + +/// +/// Represents the data needed to provide on-the-fly documentation from the symbol. +/// +/// formatted string representation of a symbol/> +/// the symbol's declaration code +/// the language of the symbol +internal sealed class OnTheFlyDocsElement(string symbolSignature, ImmutableArray declarationCode, string language) +{ + public string SymbolSignature { get; } = symbolSignature; + public ImmutableArray DeclarationCode { get; } = declarationCode; + public string Language { get; } = language; +} diff --git a/src/Features/Core/Portable/QuickInfo/QuickInfoItem.cs b/src/Features/Core/Portable/QuickInfo/QuickInfoItem.cs index ad1e8ddf430e6..8c31c4d586d83 100644 --- a/src/Features/Core/Portable/QuickInfo/QuickInfoItem.cs +++ b/src/Features/Core/Portable/QuickInfo/QuickInfoItem.cs @@ -30,16 +30,20 @@ public sealed class QuickInfoItem /// public ImmutableArray RelatedSpans { get; } + internal OnTheFlyDocsElement? OnTheFlyDocsElement { get; } + private QuickInfoItem( TextSpan span, ImmutableArray tags, ImmutableArray sections, - ImmutableArray relatedSpans) + ImmutableArray relatedSpans, + OnTheFlyDocsElement? onTheFlyDocsElement) { Span = span; Tags = tags.IsDefault ? [] : tags; Sections = sections.IsDefault ? [] : sections; RelatedSpans = relatedSpans.IsDefault ? [] : relatedSpans; + OnTheFlyDocsElement = onTheFlyDocsElement; } public static QuickInfoItem Create( @@ -48,6 +52,16 @@ public static QuickInfoItem Create( ImmutableArray sections = default, ImmutableArray relatedSpans = default) { - return new QuickInfoItem(span, tags, sections, relatedSpans); + return Create(span, tags, sections, relatedSpans, onTheFlyDocsElement: null); + } + + internal static QuickInfoItem Create( + TextSpan span, + ImmutableArray tags, + ImmutableArray sections, + ImmutableArray relatedSpans, + OnTheFlyDocsElement? onTheFlyDocsElement) + { + return new QuickInfoItem(span, tags, sections, relatedSpans, onTheFlyDocsElement); } } diff --git a/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs b/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs index cb25aef91c6fc..9ca249e914acc 100644 --- a/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs +++ b/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.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. -using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; @@ -22,7 +19,7 @@ namespace Microsoft.CodeAnalysis.QuickInfo; internal static class QuickInfoUtilities { public static Task CreateQuickInfoItemAsync(SolutionServices services, SemanticModel semanticModel, TextSpan span, ImmutableArray symbols, SymbolDescriptionOptions options, CancellationToken cancellationToken) - => CreateQuickInfoItemAsync(services, semanticModel, span, symbols, supportedPlatforms: null, showAwaitReturn: false, flowState: NullableFlowState.None, options, cancellationToken); + => CreateQuickInfoItemAsync(services, semanticModel, span, symbols, supportedPlatforms: null, showAwaitReturn: false, flowState: NullableFlowState.None, options, onTheFlyDocsElement: null, cancellationToken); public static async Task CreateQuickInfoItemAsync( SolutionServices services, @@ -33,6 +30,7 @@ public static async Task CreateQuickInfoItemAsync( bool showAwaitReturn, NullableFlowState flowState, SymbolDescriptionOptions options, + OnTheFlyDocsElement? onTheFlyDocsElement, CancellationToken cancellationToken) { var descriptionService = services.GetRequiredLanguageService(semanticModel.Language); @@ -152,7 +150,7 @@ public static async Task CreateQuickInfoItemAsync( if (supportedPlatforms?.HasValidAndInvalidProjects() == true) tags = tags.Add(WellKnownTags.Warning); - return QuickInfoItem.Create(span, tags, sections.ToImmutable()); + return QuickInfoItem.Create(span, tags, sections.ToImmutable(), relatedSpans: default, onTheFlyDocsElement); bool TryGetGroupText(SymbolDescriptionGroups group, out ImmutableArray taggedParts) => groups.TryGetValue(group, out taggedParts) && !taggedParts.IsDefaultOrEmpty; diff --git a/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs b/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs index ee949d13fc251..d46f18607c8b7 100644 --- a/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs @@ -164,7 +164,7 @@ private static async Task ReplaceMethodsWithPropertyAsync( var getMethodReferences = await SymbolFinder.FindReferencesAsync( getMethod, originalSolution, cancellationToken).ConfigureAwait(false); var setMethodReferences = setMethod == null - ? SpecializedCollections.EmptyEnumerable() + ? [] : await SymbolFinder.FindReferencesAsync( setMethod, originalSolution, cancellationToken).ConfigureAwait(false); @@ -424,7 +424,7 @@ private static async Task> GetGetSetPairsAsync( } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static TSymbol? GetSymbolInCurrentCompilation(Compilation compilation, TSymbol originalDefinition, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/ReplacePropertyWithMethods/AbstractReplacePropertyWithMethodsService.cs b/src/Features/Core/Portable/ReplacePropertyWithMethods/AbstractReplacePropertyWithMethodsService.cs index 06a711b6b28f9..cf856cb40a3bf 100644 --- a/src/Features/Core/Portable/ReplacePropertyWithMethods/AbstractReplacePropertyWithMethodsService.cs +++ b/src/Features/Core/Portable/ReplacePropertyWithMethods/AbstractReplacePropertyWithMethodsService.cs @@ -399,7 +399,7 @@ private TExpressionSyntax GetInvocationExpression( var updatedExpression = _expression.ReplaceNode(_identifierName, newIdentifierName); var arguments = argument == null - ? SpecializedCollections.EmptyEnumerable() + ? [] : SpecializedCollections.SingletonEnumerable(argument); var invocation = Generator.InvocationExpression(updatedExpression, arguments); diff --git a/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs b/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs index 035108f29721b..32eecb93707fd 100644 --- a/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ReplacePropertyWithMethods/ReplacePropertyWithMethodsCodeRefactoringProvider.cs @@ -412,7 +412,7 @@ private static async Task ReplaceDefinitionsWithMethodsAsync( result.Add((property, declaration)); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static async Task GetPropertyDeclarationAsync( diff --git a/src/Features/Core/Portable/SemanticSearch/AbstractSemanticSearchService.cs b/src/Features/Core/Portable/SemanticSearch/AbstractSemanticSearchService.cs new file mode 100644 index 0000000000000..3194bd7e2095d --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/AbstractSemanticSearchService.cs @@ -0,0 +1,436 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#if NET6_0_OR_GREATER + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Tags; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal abstract partial class AbstractSemanticSearchService : ISemanticSearchService +{ + internal sealed class LoadContext() : AssemblyLoadContext("SemanticSearchLoadContext", isCollectible: true) + { + private readonly AssemblyLoadContext _current = GetLoadContext(typeof(LoadContext).Assembly)!; + + protected override Assembly? Load(AssemblyName assemblyName) + => _current.LoadFromAssemblyName(assemblyName); + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + => IntPtr.Zero; + } + + private static readonly FindReferencesSearchOptions s_findReferencesSearchOptions = new() + { + DisplayAllDefinitions = true, + }; + + private const int StackDisplayDepthLimit = 32; + + protected abstract Compilation CreateCompilation(SourceText query, IEnumerable references, SolutionServices services, out SyntaxTree queryTree, CancellationToken cancellationToken); + + public async Task ExecuteQueryAsync( + Solution solution, + string query, + string referenceAssembliesDir, + ISemanticSearchResultsObserver observer, + OptionsProvider classificationOptions, + TraceSource traceSource, + CancellationToken cancellationToken) + { + try + { + // add progress items - one for compilation, one for emit and one for each project: + var remainingProgressItemCount = 2 + solution.ProjectIds.Count; + await observer.AddItemsAsync(remainingProgressItemCount, cancellationToken).ConfigureAwait(false); + + var metadataService = solution.Services.GetRequiredService(); + var metadataReferences = SemanticSearchUtilities.GetMetadataReferences(metadataService, referenceAssembliesDir); + var queryText = SemanticSearchUtilities.CreateSourceText(query); + var queryCompilation = CreateCompilation(queryText, metadataReferences, solution.Services, out var queryTree, cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); + + // complete compilation progress item: + remainingProgressItemCount--; + await observer.ItemsCompletedAsync(1, cancellationToken).ConfigureAwait(false); + + var emitOptions = new EmitOptions( + debugInformationFormat: DebugInformationFormat.PortablePdb, + instrumentationKinds: [InstrumentationKind.StackOverflowProbing, InstrumentationKind.ModuleCancellation]); + + using var peStream = new MemoryStream(); + using var pdbStream = new MemoryStream(); + + var emitDifferenceTimer = SharedStopwatch.StartNew(); + var emitResult = queryCompilation.Emit(peStream, pdbStream, options: emitOptions, cancellationToken: cancellationToken); + var emitTime = emitDifferenceTimer.Elapsed; + + var executionTime = TimeSpan.Zero; + + cancellationToken.ThrowIfCancellationRequested(); + + // complete compilation progress item: + remainingProgressItemCount--; + await observer.ItemsCompletedAsync(1, cancellationToken).ConfigureAwait(false); + + if (!emitResult.Success) + { + foreach (var diagnostic in emitResult.Diagnostics) + { + if (diagnostic.Severity == DiagnosticSeverity.Error) + { + traceSource.TraceInformation($"Semantic search query compilation failed: {diagnostic}"); + } + } + + await observer.OnCompilationFailureAsync( + emitResult.Diagnostics.SelectAsArray( + d => d.Severity == DiagnosticSeverity.Error, + d => new QueryCompilationError(d.Id, d.GetMessage(), (d.Location.SourceTree == queryTree) ? d.Location.SourceSpan : default)), + cancellationToken).ConfigureAwait(false); + + return CreateResult(FeaturesResources.Semantic_search_query_failed_to_compile); + } + + peStream.Position = 0; + pdbStream.Position = 0; + var loadContext = new LoadContext(); + try + { + var queryAssembly = loadContext.LoadFromStream(peStream, pdbStream); + + var pidType = queryAssembly.GetType("", throwOnError: true); + Contract.ThrowIfNull(pidType); + var moduleCancellationTokenField = pidType.GetField("ModuleCancellationToken", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + Contract.ThrowIfNull(moduleCancellationTokenField); + moduleCancellationTokenField.SetValue(null, cancellationToken); + + if (!TryGetFindMethod(queryAssembly, out var findMethod, out var errorMessage, out var errorMessageArgs)) + { + traceSource.TraceInformation($"Semantic search failed: {errorMessage}"); + return CreateResult(errorMessage, errorMessageArgs); + } + + var executionTimeStopWatch = new Stopwatch(); + + foreach (var project in solution.Projects) + { + cancellationToken.ThrowIfCancellationRequested(); + + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + try + { + executionTimeStopWatch.Start(); + + try + { + var symbols = (IEnumerable?)findMethod.Invoke(null, [compilation]) ?? []; + + foreach (var symbol in symbols) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (symbol != null) + { + executionTimeStopWatch.Stop(); + + try + { + var definitionItem = await symbol.ToClassifiedDefinitionItemAsync( + classificationOptions, solution, s_findReferencesSearchOptions, isPrimary: true, includeHiddenLocations: false, cancellationToken).ConfigureAwait(false); + + await observer.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + // skip symbol + } + + executionTimeStopWatch.Start(); + } + } + } + finally + { + executionTimeStopWatch.Stop(); + executionTime = executionTimeStopWatch.Elapsed; + } + } + catch (Exception e) when (e is not OperationCanceledException) + { + // exception from user code + + if (e is TargetInvocationException { InnerException: { } innerException }) + { + e = innerException; + } + + var (projectName, projectFlavor) = project.State.NameAndFlavor; + projectName ??= project.Name; + var projectDisplay = string.IsNullOrEmpty(projectFlavor) ? projectName : $"{projectName} ({projectFlavor})"; + + FormatStackTrace(e, queryAssembly, out var position, out var stackTraceTaggedText); + var span = queryText.Lines.GetTextSpan(new LinePositionSpan(position, position)); + + var exceptionNameTaggedText = GetExceptionTypeTaggedText(e, compilation); + + await observer.OnUserCodeExceptionAsync(new UserCodeExceptionInfo(projectDisplay, e.Message, exceptionNameTaggedText, stackTraceTaggedText, span), cancellationToken).ConfigureAwait(false); + + traceSource.TraceInformation($"Semantic query execution failed due to user code exception: {e}"); + return CreateResult(FeaturesResources.Semantic_search_query_terminated_with_exception); + } + + // complete project progress item: + remainingProgressItemCount--; + await observer.ItemsCompletedAsync(1, cancellationToken).ConfigureAwait(false); + } + } + finally + { + loadContext.Unload(); + + // complete the remaining items (in case the search gets interrupted) + if (remainingProgressItemCount > 0) + { + await observer.ItemsCompletedAsync(remainingProgressItemCount, cancellationToken).ConfigureAwait(false); + } + } + + return CreateResult(errorMessage: null); + + ExecuteQueryResult CreateResult(string? errorMessage, params string[]? args) + => new(errorMessage, args, emitTime, executionTime); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) + { + throw ExceptionUtilities.Unreachable(); + } + } + + private static ImmutableArray GetExceptionTypeTaggedText(Exception e, Compilation compilation) + => e.GetType().FullName is { } exceptionTypeName + ? compilation.GetTypeByMetadataName(exceptionTypeName) is { } exceptionTypeSymbol + ? exceptionTypeSymbol.ToDisplayParts(SymbolDisplayFormat.MinimallyQualifiedFormat).ToTaggedText() + : [new TaggedText(WellKnownTags.Class, exceptionTypeName)] + : [new TaggedText(WellKnownTags.Class, nameof(Exception))]; + + private static void FormatStackTrace(Exception e, Assembly queryAssembly, out LinePosition position, out ImmutableArray formattedTrace) + { + position = default; + + try + { + var trace = new StackTrace(e, fNeedFileInfo: true); + var frames = trace.GetFrames(); + var displayFrames = frames; + var skippedFrameCount = 0; + + try + { + var hostAssembly = typeof(AbstractSemanticSearchService).Assembly; + var displayFramesEnd = frames.Length; + var foundPosition = false; + for (var i = 0; i < frames.Length; i++) + { + var frame = frames[i]; + + if (frame.GetMethod() is { } method) + { + var frameAssembly = method.DeclaringType?.Assembly; + if (frameAssembly == hostAssembly) + { + displayFramesEnd = i; + break; + } + + if (!foundPosition && + frameAssembly == queryAssembly && + frame.GetFileName() is { } fileName && + frame.GetFileLineNumber() is > 0 and var line && + frame.GetFileColumnNumber() is > 0 and var column) + { + position = new LinePosition(line - 1, column - 1); + foundPosition = true; + } + } + } + + // display last StackDisplayDepthLimit frames preceding the host frame: + skippedFrameCount = Math.Max(0, displayFramesEnd - StackDisplayDepthLimit); + displayFrames = frames[skippedFrameCount..displayFramesEnd]; + } + catch + { + // nop + } + + formattedTrace = + [ + new TaggedText(tag: TextTags.Text, (skippedFrameCount > 0 ? " ..." + Environment.NewLine : "") + GetStackTraceText(displayFrames)) + ]; + } + catch + { + formattedTrace = []; + } + } + + private static string GetStackTraceText(IEnumerable frames) + { +#if NET8_0_OR_GREATER + return new StackTrace(frames).ToString(); +#else + var builder = new StringBuilder(); + foreach (var frame in frames) + { + builder.Append(new StackTrace(frame).ToString()); + } + + return builder.ToString(); +#endif + } + + private static bool TryGetFindMethod(Assembly queryAssembly, [NotNullWhen(true)] out MethodInfo? method, out string? error, out string[]? errorMessageArgs) + { + // TODO: Use Compilation APIs to find the method + + method = null; + error = null; + errorMessageArgs = null; + + Type? program; + try + { + program = queryAssembly.GetType(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName, throwOnError: false); + } + catch (Exception e) + { + error = FeaturesResources.Unable_to_load_type_0_1; + errorMessageArgs = [WellKnownMemberNames.TopLevelStatementsEntryPointTypeName, e.Message]; + return false; + } + + if (program != null) + { + try + { + method = GetFindMethod(program, allowLocalFunction: true, ref error); + } + catch + { + } + + if (method != null) + { + return true; + } + } + + Type[] types; + try + { + types = queryAssembly.GetTypes(); + } + catch (TypeLoadException e) + { + error = FeaturesResources.Unable_to_load_type_0_1; + errorMessageArgs = [e.TypeName, e.Message]; + method = null; + return false; + } + + foreach (var type in types) + { + method = GetFindMethod(type, allowLocalFunction: false, ref error); + if (method != null) + { + return true; + } + } + + error ??= string.Format(FeaturesResources.The_query_does_not_specify_0_method_or_top_level_function, SemanticSearchUtilities.FindMethodName); + return false; + } + + private static MethodInfo? GetFindMethod(Type type, bool allowLocalFunction, ref string? error) + { + try + { + var candidates = new ArrayBuilder(); + + foreach (var candidate in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)) + { + if (candidate.Name == SemanticSearchUtilities.FindMethodName || + allowLocalFunction && candidate.Name.StartsWith($"<{WellKnownMemberNames.TopLevelStatementsEntryPointMethodName}>g__{SemanticSearchUtilities.FindMethodName}|")) + { + candidates.Add(candidate); + } + } + + if (candidates is []) + { + error = string.Format(FeaturesResources.The_query_does_not_specify_0_method_or_top_level_function, SemanticSearchUtilities.FindMethodName); + return null; + } + + candidates.RemoveAll(candidate => candidate.IsGenericMethod || !candidate.IsStatic); + if (candidates is []) + { + error = string.Format(FeaturesResources.Method_0_must_be_static_and_non_generic, SemanticSearchUtilities.FindMethodName); + return null; + } + + candidates.RemoveAll(candidate => !( + typeof(IEnumerable).IsAssignableFrom(candidate.ReturnType) && + candidate.GetParameters() is [{ ParameterType: var paramType }] && + typeof(Compilation).IsAssignableFrom(paramType))); + + if (candidates is []) + { + error = string.Format(FeaturesResources.Method_0_must_have_a_single_parameter_of_type_1_and_return_2, SemanticSearchUtilities.FindMethodName, nameof(Compilation)); + return null; + } + + Debug.Assert(candidates.Count == 1); + return candidates[0]; + } + catch (Exception e) + { + error = e.Message; + return null; + } + } +} +#endif diff --git a/src/Features/Core/Portable/SemanticSearch/ExecuteQueryResult.cs b/src/Features/Core/Portable/SemanticSearch/ExecuteQueryResult.cs new file mode 100644 index 0000000000000..eff18ccc9528c --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/ExecuteQueryResult.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.Serialization; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +/// +/// The result of Semantic Search query execution. +/// +/// An error message if the execution failed. +/// +/// Arguments to be substituted to . +/// Use when the values may contain PII that needs to be obscured in telemetry. +/// Otherwise, should contain the formatted message. +/// +/// Time it took to emit the query compilation. +/// Time it took to execute the query. +[DataContract] +internal readonly record struct ExecuteQueryResult( + [property: DataMember(Order = 0)] string? ErrorMessage, + [property: DataMember(Order = 1)] string[]? ErrorMessageArgs = null, + [property: DataMember(Order = 2)] TimeSpan EmitTime = default, + [property: DataMember(Order = 3)] TimeSpan ExecutionTime = default); diff --git a/src/Features/Core/Portable/SemanticSearch/IRemoteSemanticSearchService.cs b/src/Features/Core/Portable/SemanticSearch/IRemoteSemanticSearchService.cs new file mode 100644 index 0000000000000..f967dc4ec9eac --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/IRemoteSemanticSearchService.cs @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more 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.Classification; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Remote; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal interface IRemoteSemanticSearchService +{ + internal interface ICallback + { + ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableDefinitionItem definition, CancellationToken cancellationToken); + ValueTask OnUserCodeExceptionAsync(RemoteServiceCallbackId callbackId, UserCodeExceptionInfo exception, CancellationToken cancellationToken); + ValueTask OnCompilationFailureAsync(RemoteServiceCallbackId callbackId, ImmutableArray errors, CancellationToken cancellationToken); + ValueTask GetClassificationOptionsAsync(RemoteServiceCallbackId callbackId, string language, CancellationToken cancellationToken); + ValueTask AddItemsAsync(RemoteServiceCallbackId callbackId, int itemCount, CancellationToken cancellationToken); + ValueTask ItemsCompletedAsync(RemoteServiceCallbackId callbackId, int itemCount, CancellationToken cancellationToken); + } + + ValueTask ExecuteQueryAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, string language, string query, string referenceAssembliesDir, CancellationToken cancellationToken); +} + +internal static class RemoteSemanticSearchServiceProxy +{ + [ExportRemoteServiceCallbackDispatcher(typeof(IRemoteSemanticSearchService)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class CallbackDispatcher() : RemoteServiceCallbackDispatcher, IRemoteSemanticSearchService.ICallback + { + public ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableDefinitionItem definition, CancellationToken cancellationToken) + => ((ServerCallback)GetCallback(callbackId)).OnDefinitionFoundAsync(definition, cancellationToken); + + public ValueTask OnUserCodeExceptionAsync(RemoteServiceCallbackId callbackId, UserCodeExceptionInfo exception, CancellationToken cancellationToken) + => ((ServerCallback)GetCallback(callbackId)).OnUserCodeExceptionAsync(exception, cancellationToken); + + public ValueTask OnCompilationFailureAsync(RemoteServiceCallbackId callbackId, ImmutableArray errors, CancellationToken cancellationToken) + => ((ServerCallback)GetCallback(callbackId)).OnCompilationFailureAsync(errors, cancellationToken); + + public ValueTask AddItemsAsync(RemoteServiceCallbackId callbackId, int itemCount, CancellationToken cancellationToken) + => ((ServerCallback)GetCallback(callbackId)).AddItemsAsync(itemCount, cancellationToken); + + public ValueTask ItemsCompletedAsync(RemoteServiceCallbackId callbackId, int itemCount, CancellationToken cancellationToken) + => ((ServerCallback)GetCallback(callbackId)).ItemsCompletedAsync(itemCount, cancellationToken); + + public ValueTask GetClassificationOptionsAsync(RemoteServiceCallbackId callbackId, string language, CancellationToken cancellationToken) + => ((ServerCallback)GetCallback(callbackId)).GetClassificationOptionsAsync(language, cancellationToken); + } + + internal sealed class ServerCallback(Solution solution, ISemanticSearchResultsObserver observer, OptionsProvider classificationOptions) + { + public async ValueTask OnDefinitionFoundAsync(SerializableDefinitionItem definition, CancellationToken cancellationToken) + { + try + { + var rehydratedDefinition = await definition.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false); + await observer.OnDefinitionFoundAsync(rehydratedDefinition, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + } + } + + public async ValueTask OnUserCodeExceptionAsync(UserCodeExceptionInfo exception, CancellationToken cancellationToken) + { + try + { + await observer.OnUserCodeExceptionAsync(exception, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + } + } + + public async ValueTask OnCompilationFailureAsync(ImmutableArray errors, CancellationToken cancellationToken) + { + try + { + await observer.OnCompilationFailureAsync(errors, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + } + } + + public async ValueTask AddItemsAsync(int itemCount, CancellationToken cancellationToken) + { + try + { + await observer.AddItemsAsync(itemCount, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + } + } + + public async ValueTask ItemsCompletedAsync(int itemCount, CancellationToken cancellationToken) + { + try + { + await observer.ItemsCompletedAsync(itemCount, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + } + } + + public async ValueTask GetClassificationOptionsAsync(string language, CancellationToken cancellationToken) + { + try + { + return await classificationOptions.GetOptionsAsync(solution.Services.GetLanguageServices(language), cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + return ClassificationOptions.Default; + } + } + } + + public static async ValueTask ExecuteQueryAsync(Solution solution, string language, string query, string referenceAssembliesDir, ISemanticSearchResultsObserver results, OptionsProvider classificationOptions, CancellationToken cancellationToken) + { + var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); + if (client == null) + { + return new ExecuteQueryResult(FeaturesResources.Semantic_search_only_supported_on_net_core); + } + + var serverCallback = new ServerCallback(solution, results, classificationOptions); + + var result = await client.TryInvokeAsync( + solution, + (service, solutionInfo, callbackId, cancellationToken) => service.ExecuteQueryAsync(solutionInfo, callbackId, language, query, referenceAssembliesDir, cancellationToken), + callbackTarget: serverCallback, + cancellationToken).ConfigureAwait(false); + + return result.Value; + } +} diff --git a/src/Features/Core/Portable/SemanticSearch/ISemanticSearchResultsObserver.cs b/src/Features/Core/Portable/SemanticSearch/ISemanticSearchResultsObserver.cs new file mode 100644 index 0000000000000..078e157b8c391 --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/ISemanticSearchResultsObserver.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.Collections.Immutable; +using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal interface ISemanticSearchResultsObserver +{ + ValueTask OnUserCodeExceptionAsync(UserCodeExceptionInfo exception, CancellationToken cancellationToken); + ValueTask OnCompilationFailureAsync(ImmutableArray errors, CancellationToken cancellationToken); + ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken); + ValueTask AddItemsAsync(int itemCount, CancellationToken cancellationToken); + ValueTask ItemsCompletedAsync(int itemCount, CancellationToken cancellationToken); +} + +[DataContract] +internal readonly record struct UserCodeExceptionInfo( + [property: DataMember(Order = 0)] string ProjectDisplayName, + [property: DataMember(Order = 1)] string Message, + [property: DataMember(Order = 2)] ImmutableArray TypeName, + [property: DataMember(Order = 3)] ImmutableArray StackTrace, + [property: DataMember(Order = 4)] TextSpan Span); + +[DataContract] +internal readonly record struct QueryCompilationError( + [property: DataMember(Order = 0)] string Id, + [property: DataMember(Order = 1)] string Message, + [property: DataMember(Order = 2)] TextSpan Span); diff --git a/src/Features/Core/Portable/SemanticSearch/ISemanticSearchService.cs b/src/Features/Core/Portable/SemanticSearch/ISemanticSearchService.cs new file mode 100644 index 0000000000000..a61eccb7c134b --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/ISemanticSearchService.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal interface ISemanticSearchService : ILanguageService +{ + /// + /// Executes given query against . + /// + /// The solution snapshot. + /// Query (top-level code). + /// Directory that contains refernece assemblies to be used for compilation of the query. + /// Observer of the found symbols. + /// Options to use to classify the textual representation of the found symbols. + /// Cancellation token. + /// Error message on failure. + Task ExecuteQueryAsync( + Solution solution, + string query, + string referenceAssembliesDir, + ISemanticSearchResultsObserver observer, + OptionsProvider classificationOptions, + TraceSource traceSource, + CancellationToken cancellationToken); +} diff --git a/src/Features/Core/Portable/Diagnostics/ISupportLiveUpdate.cs b/src/Features/Core/Portable/SemanticSearch/ISemanticSearchWorkspaceHost.cs similarity index 52% rename from src/Features/Core/Portable/Diagnostics/ISupportLiveUpdate.cs rename to src/Features/Core/Portable/SemanticSearch/ISemanticSearchWorkspaceHost.cs index 3f10d2e011a39..1f6160c58c679 100644 --- a/src/Features/Core/Portable/Diagnostics/ISupportLiveUpdate.cs +++ b/src/Features/Core/Portable/SemanticSearch/ISemanticSearchWorkspaceHost.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. -namespace Microsoft.CodeAnalysis.Diagnostics; +namespace Microsoft.CodeAnalysis.SemanticSearch; /// -/// Marker interface to indicate whether given diagnostic args are from live analysis. +/// Provides access to singleton. /// -internal interface ISupportLiveUpdate +internal interface ISemanticSearchWorkspaceHost { + SemanticSearchWorkspace Workspace { get; } } diff --git a/src/Features/Core/Portable/SemanticSearch/SearchCompilationFailureDefinitionItem.cs b/src/Features/Core/Portable/SemanticSearch/SearchCompilationFailureDefinitionItem.cs new file mode 100644 index 0000000000000..a989618d1f0c2 --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/SearchCompilationFailureDefinitionItem.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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Tags; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal sealed class SearchCompilationFailureDefinitionItem(QueryCompilationError error, Document queryDocument) + : DefinitionItem( + tags: + [ + WellKnownTags.Error + ], + displayParts: + [ + new TaggedText(TextTags.Text, error.Id), + new TaggedText(TextTags.Punctuation, ":"), + new TaggedText(TextTags.Space, " "), + new TaggedText(TextTags.Text, error.Message) + ], + nameDisplayParts: [], + sourceSpans: + [ + new DocumentSpan(queryDocument, error.Span) + ], + classifiedSpans: [], + metadataLocations: [], + properties: null, + displayableProperties: null, + displayIfNoReferences: true) +{ + internal override bool IsExternal => false; + + public override Task GetNavigableLocationAsync(Workspace workspace, CancellationToken cancellationToken) + => Task.FromResult(null); +} + diff --git a/src/Features/Core/Portable/SemanticSearch/SearchExceptionDefinitionItem.cs b/src/Features/Core/Portable/SemanticSearch/SearchExceptionDefinitionItem.cs new file mode 100644 index 0000000000000..3318f13ed1bf0 --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/SearchExceptionDefinitionItem.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more 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.FindUsages; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Tags; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal sealed class SearchExceptionDefinitionItem(string message, ImmutableArray exceptionTypeName, ImmutableArray stackTrace, DocumentSpan documentSpan) + : DefinitionItem( + tags: + [ + WellKnownTags.Error + ], + displayParts: + [ + .. exceptionTypeName, + new TaggedText(TextTags.Punctuation, ":"), + new TaggedText(TextTags.Space, " "), + new TaggedText(TextTags.StringLiteral, message), + new TaggedText(TextTags.Space, Environment.NewLine), + .. stackTrace + ], + nameDisplayParts: exceptionTypeName, + sourceSpans: + [ + documentSpan + ], + classifiedSpans: [], + metadataLocations: [], + properties: null, + displayableProperties: null, + displayIfNoReferences: true) +{ + internal override bool IsExternal => false; + + public override Task GetNavigableLocationAsync(Workspace workspace, CancellationToken cancellationToken) + => Task.FromResult(null); +} + diff --git a/src/Features/Core/Portable/SemanticSearch/SemanticSearchDocumentSupportsFeatureService.cs b/src/Features/Core/Portable/SemanticSearch/SemanticSearchDocumentSupportsFeatureService.cs new file mode 100644 index 0000000000000..bdee370068f02 --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/SemanticSearchDocumentSupportsFeatureService.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 Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +[ExportWorkspaceService(typeof(IDocumentSupportsFeatureService), WorkspaceKind.SemanticSearch), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class SemanticSearchDocumentSupportsFeatureService() : IDocumentSupportsFeatureService +{ + public bool SupportsCodeFixes(Document document) + => SemanticSearchUtilities.IsQueryDocument(document); + + public bool SupportsRefactorings(Document document) + => SemanticSearchUtilities.IsQueryDocument(document); + + public bool SupportsRename(Document document) + => SemanticSearchUtilities.IsQueryDocument(document); + + public bool SupportsNavigationToAnyPosition(Document document) + => SemanticSearchUtilities.IsQueryDocument(document); + + public bool SupportsSemanticSnippets(Document document) + => SemanticSearchUtilities.IsQueryDocument(document); +} diff --git a/src/Features/Core/Portable/SemanticSearch/SemanticSearchProjectConfiguration.cs b/src/Features/Core/Portable/SemanticSearch/SemanticSearchProjectConfiguration.cs new file mode 100644 index 0000000000000..99485a2a7717b --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/SemanticSearchProjectConfiguration.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. + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal sealed class SemanticSearchProjectConfiguration +{ + public required string Language { get; init; } + public required string Query { get; init; } + public required string GlobalUsings { get; init; } + public required string EditorConfig { get; init; } + public required ParseOptions ParseOptions { get; init; } + public required CompilationOptions CompilationOptions { get; init; } +} diff --git a/src/Features/Core/Portable/SemanticSearch/SemanticSearchUtilities.cs b/src/Features/Core/Portable/SemanticSearch/SemanticSearchUtilities.cs new file mode 100644 index 0000000000000..78cb3276b171a --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/SemanticSearchUtilities.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more 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.IO; +using System.Text; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Shared.Extensions; +using System.Linq; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal static class SemanticSearchUtilities +{ + public const string ReferenceAssemblyDirectoryName = "SemanticSearchRefs"; + public const string GlobalUsingsDocumentName = "GlobalUsings"; + public const string ConfigDocumentName = ".editorconfig"; + public const string FindMethodName = "Find"; + + public static readonly string QueryProjectName = FeaturesResources.SemanticSearch; + public static readonly string QueryDocumentName = FeaturesResources.Query; + + private static readonly string s_thisAssemblyDirectory = Path.GetDirectoryName(typeof(SemanticSearchUtilities).Assembly.Location!)!; + public static readonly string ReferenceAssembliesDirectory = Path.Combine(s_thisAssemblyDirectory, ReferenceAssemblyDirectoryName); + + public static List GetMetadataReferences(IMetadataService metadataService, string directory) + { + // TODO: https://github.com/dotnet/roslyn/issues/72585 + + var metadataReferences = new List(); + try + { + foreach (var path in Directory.EnumerateFiles(directory, "*.dll", SearchOption.TopDirectoryOnly)) + { + try + { + metadataReferences.Add(metadataService.GetReference(path, MetadataReferenceProperties.Assembly)); + } + catch + { + continue; + } + } + } + catch (Exception e) when (FatalError.ReportAndCatch(e, ErrorSeverity.Diagnostic)) + { + metadataReferences = []; + } + + return metadataReferences; + } + + public static string GetDocumentFilePath(string language) + => Path.Combine(s_thisAssemblyDirectory, QueryDocumentName + (language == LanguageNames.CSharp ? ".cs" : ".vb")); + + public static string GetConfigDocumentFilePath() + => Path.Combine(s_thisAssemblyDirectory, ConfigDocumentName); + + public static SourceText CreateSourceText(string query) + => SourceText.From(query, Encoding.UTF8, SourceHashAlgorithm.Sha256); + + public static Document GetQueryDocument(Solution solution) + => solution.GetRequiredDocument(GetQueryDocumentId(solution)); + + public static ProjectId GetQueryProjectId(Solution solution) + => solution.ProjectIds.Single(); + + public static Project GetQueryProject(Solution solution) + => solution.Projects.Single(); + + public static DocumentId GetQueryDocumentId(Solution solution) + => GetQueryProject(solution).DocumentIds[0]; + + public static bool IsQueryDocument(Document document) + => GetQueryDocumentId(document.Project.Solution) == document.Id; +} diff --git a/src/Features/Core/Portable/SemanticSearch/SemanticSearchWorkspace.cs b/src/Features/Core/Portable/SemanticSearch/SemanticSearchWorkspace.cs new file mode 100644 index 0000000000000..beeda8030c65c --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/SemanticSearchWorkspace.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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +internal abstract class SemanticSearchWorkspace(HostServices services, SemanticSearchProjectConfiguration config) + : Workspace(services, WorkspaceKind.SemanticSearch) +{ + public override bool CanOpenDocuments + => true; + + public override bool CanApplyChange(ApplyChangesKind feature) + => feature == ApplyChangesKind.ChangeDocument; + + public async Task UpdateQueryDocumentAsync(string? query, CancellationToken cancellationToken) + { + SourceText? newText = null; + + var (_, newSolution) = await SetCurrentSolutionAsync( + useAsync: true, + transformation: oldSolution => + { + if (oldSolution.Projects.Any()) + { + if (query == null) + { + // already have a content, don't reset it to default: + return oldSolution; + } + + newText = SemanticSearchUtilities.CreateSourceText(query); + return oldSolution.WithDocumentText(SemanticSearchUtilities.GetQueryDocumentId(oldSolution), newText); + } + + newText = SemanticSearchUtilities.CreateSourceText(query ?? config.Query); + var metadataService = oldSolution.Services.GetRequiredService(); + + return oldSolution + .AddProject(name: SemanticSearchUtilities.QueryProjectName, assemblyName: SemanticSearchUtilities.QueryProjectName, config.Language) + .WithCompilationOptions(config.CompilationOptions) + .WithParseOptions(config.ParseOptions) + .AddMetadataReferences(SemanticSearchUtilities.GetMetadataReferences(metadataService, SemanticSearchUtilities.ReferenceAssembliesDirectory)) + .AddDocument(name: SemanticSearchUtilities.QueryDocumentName, newText, filePath: SemanticSearchUtilities.GetDocumentFilePath(config.Language)).Project + .AddDocument(name: SemanticSearchUtilities.GlobalUsingsDocumentName, SemanticSearchUtilities.CreateSourceText(config.GlobalUsings), filePath: null).Project + .AddAnalyzerConfigDocument(name: SemanticSearchUtilities.ConfigDocumentName, SemanticSearchUtilities.CreateSourceText(config.EditorConfig), filePath: SemanticSearchUtilities.GetConfigDocumentFilePath()).Project.Solution; + }, + changeKind: (oldSolution, newSolution) => + oldSolution.Projects.Any() + ? (WorkspaceChangeKind.DocumentChanged, projectId: null, documentId: SemanticSearchUtilities.GetQueryDocumentId(newSolution)) + : (WorkspaceChangeKind.ProjectAdded, projectId: SemanticSearchUtilities.GetQueryProjectId(newSolution), documentId: null), + onBeforeUpdate: null, + onAfterUpdate: null, + cancellationToken).ConfigureAwait(false); + + var queryDocument = SemanticSearchUtilities.GetQueryDocument(newSolution); + + if (newText != null) + { + ApplyQueryDocumentTextChanged(newText); + } + + return queryDocument; + } + + protected virtual void ApplyQueryDocumentTextChanged(SourceText newText) + { + } +} diff --git a/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs b/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs index 4e1f9c472db23..4326eef9430da 100644 --- a/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs +++ b/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs @@ -68,7 +68,7 @@ public static async Task> GetUnionItemsFromDocumentAndLinkedDo totalItems.AddRange(values.NullToEmpty()); } - return totalItems.ToImmutableArray(); + return [.. totalItems]; } public static async Task IsValidContextForDocumentOrLinkedDocumentsAsync( diff --git a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs index 8043821cf95c2..c871967799ac1 100644 --- a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs +++ b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs @@ -36,8 +36,7 @@ public static ImmutableArray Sort( var symbolToParameterTypeNames = new ConcurrentDictionary(); string[] getParameterTypeNames(TSymbol s) => GetParameterTypeNames(s, semanticModel, position); - return symbols.OrderBy((s1, s2) => Compare(s1, s2, symbolToParameterTypeNames, getParameterTypeNames)) - .ToImmutableArray(); + return [.. symbols.OrderBy((s1, s2) => Compare(s1, s2, symbolToParameterTypeNames, getParameterTypeNames))]; } private static INamedTypeSymbol GetNamedType(ITypeSymbol type) diff --git a/src/Features/Core/Portable/Shared/Utilities/ExtractTypeHelpers.cs b/src/Features/Core/Portable/Shared/Utilities/ExtractTypeHelpers.cs index 574e0035ee1ce..fb6c7614baeab 100644 --- a/src/Features/Core/Portable/Shared/Utilities/ExtractTypeHelpers.cs +++ b/src/Features/Core/Portable/Shared/Utilities/ExtractTypeHelpers.cs @@ -180,7 +180,7 @@ private static ImmutableArray GetPotentialTypeParameters(I typeParameters.AddRange(typesToVisit.Pop().TypeParameters); } - return typeParameters.ToImmutable(); + return typeParameters.ToImmutableAndClear(); } private static ImmutableArray GetDirectlyReferencedTypeParameters(IEnumerable potentialTypeParameters, IEnumerable includedMembers) @@ -194,7 +194,7 @@ private static ImmutableArray GetDirectlyReferencedTypePar } } - return directlyReferencedTypeParameters.ToImmutable(); + return directlyReferencedTypeParameters.ToImmutableAndClear(); } private static bool DoesMemberReferenceTypeParameter(ISymbol member, ITypeParameterSymbol typeParameter, HashSet checkedTypes) diff --git a/src/Features/Core/Portable/Shared/Utilities/SupportedPlatformData.cs b/src/Features/Core/Portable/Shared/Utilities/SupportedPlatformData.cs index 06b3e9b8db311..e03becf494801 100644 --- a/src/Features/Core/Portable/Shared/Utilities/SupportedPlatformData.cs +++ b/src/Features/Core/Portable/Shared/Utilities/SupportedPlatformData.cs @@ -23,9 +23,7 @@ internal sealed class SupportedPlatformData(Solution solution, List i public IList ToDisplayParts() { if (InvalidProjects == null || InvalidProjects.Count == 0) - { - return SpecializedCollections.EmptyList(); - } + return []; var builder = new List(); builder.AddLineBreak(); diff --git a/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs b/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs index 9016af082906d..a2c755e5f1f09 100644 --- a/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs +++ b/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs @@ -101,7 +101,7 @@ private static (IList items, int? selectedItem) Filter(IList< var filteredList = items.Where(i => Include(i, parameterNames)).ToList(); var isEmpty = filteredList.Count == 0; if (!selectedItem.HasValue || isEmpty) - return (isEmpty ? items.ToList() : filteredList, selectedItem); + return (isEmpty ? [.. items] : filteredList, selectedItem); // adjust the selected item var selection = items[selectedItem.Value]; @@ -313,7 +313,7 @@ private static async Task> FindActiveRelatedDocumentsAs } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private static SignatureHelpItem UpdateItem(SignatureHelpItem item, SupportedPlatformData platformData) diff --git a/src/Features/Core/Portable/SignatureHelp/SignatureHelpItem.cs b/src/Features/Core/Portable/SignatureHelp/SignatureHelpItem.cs index 134503519a861..35ff80955b3ab 100644 --- a/src/Features/Core/Portable/SignatureHelp/SignatureHelpItem.cs +++ b/src/Features/Core/Portable/SignatureHelp/SignatureHelpItem.cs @@ -35,8 +35,7 @@ internal class SignatureHelpItem public Func> DocumentationFactory { get; } - private static readonly Func> s_emptyDocumentationFactory = - _ => SpecializedCollections.EmptyEnumerable(); + private static readonly Func> s_emptyDocumentationFactory = _ => []; public SignatureHelpItem( bool isVariadic, diff --git a/src/Features/Core/Portable/SignatureHelp/SignatureHelpParameter.cs b/src/Features/Core/Portable/SignatureHelp/SignatureHelpParameter.cs index e3b5f5523f87b..7a83abe7eed24 100644 --- a/src/Features/Core/Portable/SignatureHelp/SignatureHelpParameter.cs +++ b/src/Features/Core/Portable/SignatureHelp/SignatureHelpParameter.cs @@ -65,8 +65,7 @@ internal class SignatureHelpSymbolParameter( /// public IList SelectedDisplayParts { get; } = selectedDisplayParts.ToImmutableArrayOrEmpty(); - private static readonly Func> s_emptyDocumentationFactory = - _ => SpecializedCollections.EmptyEnumerable(); + private static readonly Func> s_emptyDocumentationFactory = _ => []; internal IEnumerable GetAllParts() { @@ -134,8 +133,7 @@ internal class SignatureHelpParameter( /// public IList SelectedDisplayParts { get; } = selectedDisplayParts.ToImmutableArrayOrEmpty(); - private static readonly Func> s_emptyDocumentationFactory = - _ => SpecializedCollections.EmptyEnumerable(); + private static readonly Func> s_emptyDocumentationFactory = _ => []; // Constructor kept for binary compat with TS. Remove when they move to the new API. public SignatureHelpParameter( diff --git a/src/Features/Core/Portable/Snippets/AbstractSnippetService.cs b/src/Features/Core/Portable/Snippets/AbstractSnippetService.cs index b61c196c6c36c..55e4cbe47b1f9 100644 --- a/src/Features/Core/Portable/Snippets/AbstractSnippetService.cs +++ b/src/Features/Core/Portable/Snippets/AbstractSnippetService.cs @@ -46,7 +46,7 @@ public async Task> GetSnippetsAsync(SnippetContext c arrayBuilder.AddIfNotNull(snippetData); } - return arrayBuilder.ToImmutable(); + return arrayBuilder.ToImmutableAndClear(); } private ImmutableArray GetSnippetProviders(Document document) diff --git a/src/Features/Core/Portable/Snippets/CommonSnippetIdentifiers.cs b/src/Features/Core/Portable/Snippets/CommonSnippetIdentifiers.cs new file mode 100644 index 0000000000000..1167581c3f826 --- /dev/null +++ b/src/Features/Core/Portable/Snippets/CommonSnippetIdentifiers.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. + +namespace Microsoft.CodeAnalysis.Snippets; + +/// +/// Contains language-neutral snippet identifiers, +/// which can theoretically be used in a snippet provider regardless its target language +/// +internal static class CommonSnippetIdentifiers +{ + public const string ConsoleWriteLine = "cw"; + public const string Constructor = "ctor"; + public const string Property = "prop"; + public const string GetOnlyProperty = "propg"; +} diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConditionalBlockSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConditionalBlockSnippetProvider.cs index a5b5f1851712f..89829127811cc 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConditionalBlockSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConditionalBlockSnippetProvider.cs @@ -11,21 +11,21 @@ namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; /// /// Base class for "if" and "while" snippet providers /// -internal abstract class AbstractConditionalBlockSnippetProvider : AbstractInlineStatementSnippetProvider +internal abstract class AbstractConditionalBlockSnippetProvider : AbstractInlineStatementSnippetProvider + where TStatementSyntax : SyntaxNode + where TExpressionSyntax : SyntaxNode { - protected abstract SyntaxNode GetCondition(SyntaxNode node); + protected abstract TExpressionSyntax GetCondition(TStatementSyntax node); - protected override bool IsValidAccessingType(ITypeSymbol type, Compilation compilation) + protected sealed override bool IsValidAccessingType(ITypeSymbol type, Compilation compilation) => type.SpecialType == SpecialType.System_Boolean; - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected sealed override ImmutableArray GetPlaceHolderLocationsList(TStatementSyntax node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { if (ConstructedFromInlineExpression) return []; var condition = GetCondition(node); - var placeholder = new SnippetPlaceholder(condition.ToString(), condition.SpanStart); - - return [placeholder]; + return [new SnippetPlaceholder(condition.ToString(), condition.SpanStart)]; } } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs index 57d80f27cd2f2..71596938c05c9 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs @@ -18,31 +18,34 @@ namespace Microsoft.CodeAnalysis.Snippets; -internal abstract class AbstractConsoleSnippetProvider : AbstractStatementSnippetProvider +internal abstract class AbstractConsoleSnippetProvider< + TExpressionStatementSyntax, + TExpressionSyntax, + TArgumentListSyntax> : AbstractStatementSnippetProvider + where TExpressionStatementSyntax : SyntaxNode + where TExpressionSyntax : SyntaxNode + where TArgumentListSyntax : SyntaxNode { - public override string Identifier => "cw"; + public sealed override string Identifier => CommonSnippetIdentifiers.ConsoleWriteLine; - public override string Description => FeaturesResources.console_writeline; + public sealed override string Description => FeaturesResources.console_writeline; - public override ImmutableArray AdditionalFilterTexts { get; } = ["WriteLine"]; + public sealed override ImmutableArray AdditionalFilterTexts { get; } = ["WriteLine"]; - protected override bool IsValidSnippetLocation(in SnippetContext context, CancellationToken cancellationToken) + protected abstract TExpressionSyntax GetExpression(TExpressionStatementSyntax expressionStatement); + protected abstract TArgumentListSyntax GetArgumentList(TExpressionSyntax expression); + protected abstract SyntaxToken GetOpenParenToken(TArgumentListSyntax argumentList); + + protected sealed override bool IsValidSnippetLocation(in SnippetContext context, CancellationToken cancellationToken) { var consoleSymbol = GetConsoleSymbolFromMetaDataName(context.SyntaxContext.SemanticModel.Compilation); if (consoleSymbol is null) - { return false; - } return base.IsValidSnippetLocation(in context, cancellationToken); } - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) - { - return syntaxFacts.IsExpressionStatement; - } - - protected override Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) + protected sealed override Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) { var generator = SyntaxGenerator.GetGenerator(document); @@ -57,81 +60,51 @@ protected override Task GenerateSnippetTextChangeAsync(Document docu /// Tries to get the location after the open parentheses in the argument list. /// If it can't, then we default to the end of the snippet's span. /// - protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText) + protected sealed override int GetTargetCaretPosition(TExpressionStatementSyntax caretTarget, SourceText sourceText) { - var invocationExpression = caretTarget.DescendantNodes().Where(syntaxFacts.IsInvocationExpression).FirstOrDefault(); + var invocationExpression = GetExpression(caretTarget); if (invocationExpression is null) - { return caretTarget.Span.End; - } - var argumentListNode = syntaxFacts.GetArgumentListOfInvocationExpression(invocationExpression); + var argumentListNode = GetArgumentList(invocationExpression); if (argumentListNode is null) - { return caretTarget.Span.End; - } - syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _); + var openParenToken = GetOpenParenToken(argumentListNode); return openParenToken.Span.End; } - protected override async Task AnnotateNodesToReformatAsync(Document document, - SyntaxAnnotation findSnippetAnnotation, SyntaxAnnotation cursorAnnotation, int position, CancellationToken cancellationToken) + protected sealed override async Task AnnotateNodesToReformatAsync( + Document document, int position, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var syntaxFacts = document.GetRequiredLanguageService(); - var snippetExpressionNode = FindAddedSnippetSyntaxNode(root, position, syntaxFacts.IsExpressionStatement); + var snippetExpressionNode = FindAddedSnippetSyntaxNode(root, position); Contract.ThrowIfNull(snippetExpressionNode); var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var consoleSymbol = GetConsoleSymbolFromMetaDataName(compilation); - var reformatSnippetNode = snippetExpressionNode.WithAdditionalAnnotations(findSnippetAnnotation, cursorAnnotation, Simplifier.Annotation, SymbolAnnotation.Create(consoleSymbol!), Formatter.Annotation); + var reformatSnippetNode = snippetExpressionNode.WithAdditionalAnnotations(FindSnippetAnnotation, Simplifier.Annotation, SymbolAnnotation.Create(consoleSymbol!), Formatter.Annotation); return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) - { - return []; - } - - private static SyntaxToken? GetOpenParenToken(SyntaxNode node, ISyntaxFacts syntaxFacts) - { - var invocationExpression = node.DescendantNodes().Where(syntaxFacts.IsInvocationExpression).FirstOrDefault(); - if (invocationExpression is null) - { - return null; - } - - var argumentListNode = syntaxFacts.GetArgumentListOfInvocationExpression(invocationExpression); - if (argumentListNode is null) - { - return null; - } - - syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _); - - return openParenToken; - } + protected sealed override ImmutableArray GetPlaceHolderLocationsList(TExpressionStatementSyntax node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + => []; private static INamedTypeSymbol? GetConsoleSymbolFromMetaDataName(Compilation compilation) => compilation.GetBestTypeByMetadataName(typeof(Console).FullName!); - protected override SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, Func isCorrectContainer) + protected sealed override TExpressionStatementSyntax? FindAddedSnippetSyntaxNode(SyntaxNode root, int position) { var closestNode = root.FindNode(TextSpan.FromBounds(position, position)); - var nearestExpressionStatement = closestNode.FirstAncestorOrSelf(isCorrectContainer); + var nearestExpressionStatement = closestNode.FirstAncestorOrSelf(); if (nearestExpressionStatement is null) - { return null; - } // Checking to see if that expression statement that we found is // starting at the same position as the position we inserted // the Console WriteLine expression statement. if (nearestExpressionStatement.SpanStart != position) - { return null; - } return nearestExpressionStatement; } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConstructorSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConstructorSnippetProvider.cs index 50792aaefb98d..33e33a7a1b4f0 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConstructorSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConstructorSnippetProvider.cs @@ -2,23 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Threading; using Microsoft.CodeAnalysis.LanguageService; namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; -internal abstract class AbstractConstructorSnippetProvider : AbstractSingleChangeSnippetProvider +internal abstract class AbstractConstructorSnippetProvider : AbstractSingleChangeSnippetProvider + where TConstructorDeclarationSyntax : SyntaxNode { - public override string Identifier => "ctor"; + public sealed override string Identifier => CommonSnippetIdentifiers.Constructor; - public override string Description => FeaturesResources.constructor; + public sealed override string Description => FeaturesResources.constructor; - public override ImmutableArray AdditionalFilterTexts { get; } = ["constructor"]; + public sealed override ImmutableArray AdditionalFilterTexts { get; } = ["constructor"]; - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) => syntaxFacts.IsConstructorDeclaration; - - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected sealed override ImmutableArray GetPlaceHolderLocationsList(TConstructorDeclarationSyntax node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) => []; } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractElseSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractElseSnippetProvider.cs index 89b5fe97b53d6..22c163756c430 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractElseSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractElseSnippetProvider.cs @@ -2,21 +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.Threading; using Microsoft.CodeAnalysis.LanguageService; namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; -internal abstract class AbstractElseSnippetProvider : AbstractStatementSnippetProvider +internal abstract class AbstractElseSnippetProvider : AbstractStatementSnippetProvider + where TElseClauseSyntax : SyntaxNode { - public override string Identifier => "else"; - - public override string Description => FeaturesResources.else_statement; - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) => syntaxFacts.IsElseClause; - - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected sealed override ImmutableArray GetPlaceHolderLocationsList(TElseClauseSyntax node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) => []; } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractForEachLoopSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractForEachLoopSnippetProvider.cs index c71af5d49c209..2f5c742d67e40 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractForEachLoopSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractForEachLoopSnippetProvider.cs @@ -2,24 +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. -using System; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Snippets.SnippetProviders; namespace Microsoft.CodeAnalysis.Snippets; -internal abstract class AbstractForEachLoopSnippetProvider : AbstractInlineStatementSnippetProvider +internal abstract class AbstractForEachLoopSnippetProvider : AbstractInlineStatementSnippetProvider + where TStatementSyntax : SyntaxNode { - public override string Identifier => "foreach"; - - public override string Description => FeaturesResources.foreach_loop; - - protected override bool IsValidAccessingType(ITypeSymbol type, Compilation compilation) + protected sealed override bool IsValidAccessingType(ITypeSymbol type, Compilation compilation) => type.CanBeEnumerated() || type.CanBeAsynchronouslyEnumerated(compilation); - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) - { - return syntaxFacts.IsForEachStatement; - } } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractForLoopSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractForLoopSnippetProvider.cs index 88444b5140578..73458de1f3cd9 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractForLoopSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractForLoopSnippetProvider.cs @@ -2,16 +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.Linq; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; -internal abstract class AbstractForLoopSnippetProvider : AbstractInlineStatementSnippetProvider +internal abstract class AbstractForLoopSnippetProvider : AbstractInlineStatementSnippetProvider + where TStatementSyntax : SyntaxNode { - protected override bool IsValidAccessingType(ITypeSymbol type, Compilation compilation) + protected sealed override bool IsValidAccessingType(ITypeSymbol type, Compilation compilation) { if (IsSuitableIntegerType(type)) { @@ -25,9 +24,6 @@ protected override bool IsValidAccessingType(ITypeSymbol type, Compilation compi return hasLengthProperty ^ hasCountProperty; } - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) - => syntaxFacts.IsForStatement; - protected static bool IsSuitableIntegerType(ITypeSymbol type) => type.IsIntegralType() || type.IsNativeIntegerType; diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index 4cd9cf954b66a..46c0c9e61c866 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -2,25 +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. -using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; using Microsoft.CodeAnalysis.Snippets.SnippetProviders; namespace Microsoft.CodeAnalysis.Snippets; -internal abstract class AbstractIfSnippetProvider : AbstractConditionalBlockSnippetProvider +internal abstract class AbstractIfSnippetProvider : AbstractConditionalBlockSnippetProvider + where TIfStatementSyntax : SyntaxNode + where TExpressionSyntax : SyntaxNode { - public override string Identifier => "if"; + public sealed override ImmutableArray AdditionalFilterTexts { get; } = ["statement"]; - public override string Description => FeaturesResources.if_statement; - - public override ImmutableArray AdditionalFilterTexts { get; } = ["statement"]; - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) => syntaxFacts.IsIfStatement; - - protected override SyntaxNode GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo) - => generator.IfStatement(inlineExpressionInfo?.Node.WithoutLeadingTrivia() ?? generator.TrueLiteralExpression(), []); + protected sealed override TIfStatementSyntax GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo) + => (TIfStatementSyntax)generator.IfStatement(inlineExpressionInfo?.Node.WithoutLeadingTrivia() ?? generator.TrueLiteralExpression(), []); } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractInlineStatementSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractInlineStatementSnippetProvider.cs index 128990253988b..ac60dc0569394 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractInlineStatementSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractInlineStatementSnippetProvider.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.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; @@ -18,7 +17,8 @@ namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; /// Base class for snippets, that can be both executed as normal statement snippets /// or constructed from a member access expression when accessing members of a specific type /// -internal abstract class AbstractInlineStatementSnippetProvider : AbstractStatementSnippetProvider +internal abstract class AbstractInlineStatementSnippetProvider : AbstractStatementSnippetProvider + where TStatementSyntax : SyntaxNode { /// /// Tells if accessing type of a member access expression is valid for that snippet @@ -31,7 +31,7 @@ internal abstract class AbstractInlineStatementSnippetProvider : AbstractStateme /// Generate statement node /// /// Information about inline expression or if snippet is executed in normal statement context - protected abstract SyntaxNode GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo); + protected abstract TStatementSyntax GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo); /// /// Tells whether the original snippet was constructed from member access expression. @@ -69,10 +69,10 @@ protected sealed override async Task GenerateSnippetTextChangeAsync( return new TextChange(TextSpan.FromBounds(inlineExpressionInfo?.Node.SpanStart ?? position, position), statement.ToFullString()); } - protected sealed override SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, Func isCorrectContainer) + protected sealed override TStatementSyntax? FindAddedSnippetSyntaxNode(SyntaxNode root, int position) { var closestNode = root.FindNode(TextSpan.FromBounds(position, position), getInnermostNodeForTie: true); - return closestNode.FirstAncestorOrSelf(isCorrectContainer); + return closestNode.FirstAncestorOrSelf(); } private static bool TryGetInlineExpressionInfo(SyntaxToken targetToken, ISyntaxFactsService syntaxFacts, SemanticModel semanticModel, [NotNullWhen(true)] out InlineExpressionInfo? expressionInfo, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractLockSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractLockSnippetProvider.cs index d8797403a6f18..8d1d1c2488ada 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractLockSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractLockSnippetProvider.cs @@ -2,25 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; -internal abstract class AbstractLockSnippetProvider : AbstractStatementSnippetProvider +internal abstract class AbstractLockSnippetProvider : AbstractStatementSnippetProvider + where TLockStatementSyntax : SyntaxNode { - protected override Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) + protected sealed override Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) { var generator = SyntaxGenerator.GetGenerator(document); - var statement = generator.LockStatement(generator.ThisExpression(), SpecializedCollections.EmptyEnumerable()); + var statement = generator.LockStatement(generator.ThisExpression(), statements: []); return Task.FromResult(new TextChange(TextSpan.FromBounds(position, position), statement.NormalizeWhitespace().ToFullString())); } - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) - => syntaxFacts.IsLockStatement; } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractMainMethodSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractMainMethodSnippetProvider.cs index 38f4b97f2b29d..7d00594dc0db7 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractMainMethodSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractMainMethodSnippetProvider.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.Threading; @@ -14,20 +13,23 @@ namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; -internal abstract class AbstractMainMethodSnippetProvider : AbstractSingleChangeSnippetProvider +internal abstract class AbstractMainMethodSnippetProvider : AbstractSingleChangeSnippetProvider + where TMethodDeclarationSyntax : SyntaxNode + where TStatementSyntax : SyntaxNode + where TTypeSyntax : SyntaxNode { - protected abstract SyntaxNode GenerateReturnType(SyntaxGenerator generator); + protected abstract TTypeSyntax GenerateReturnType(SyntaxGenerator generator); - protected abstract IEnumerable GenerateInnerStatements(SyntaxGenerator generator); + protected abstract IEnumerable GenerateInnerStatements(SyntaxGenerator generator); - protected override Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) + protected sealed override Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) { var generator = SyntaxGenerator.GetGenerator(document); var method = generator.MethodDeclaration( name: WellKnownMemberNames.EntryPointMethodName, - parameters: SpecializedCollections.SingletonEnumerable(generator.ParameterDeclaration( + parameters: [generator.ParameterDeclaration( name: "args", - type: generator.ArrayTypeExpression(generator.TypeExpression(SpecialType.System_String)))), + type: generator.ArrayTypeExpression(generator.TypeExpression(SpecialType.System_String)))], returnType: GenerateReturnType(generator), modifiers: DeclarationModifiers.Static, statements: GenerateInnerStatements(generator)); @@ -35,9 +37,6 @@ protected override Task GenerateSnippetTextChangeAsync(Document docu return Task.FromResult(new TextChange(TextSpan.FromBounds(position, position), method.NormalizeWhitespace().ToFullString())); } - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected sealed override ImmutableArray GetPlaceHolderLocationsList(TMethodDeclarationSyntax node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) => []; - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) - => syntaxFacts.IsMethodDeclaration; } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractPropertySnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractPropertySnippetProvider.cs index c460de680c2da..3aaf3e2668e8b 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractPropertySnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractPropertySnippetProvider.cs @@ -2,31 +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. -using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; -internal abstract class AbstractPropertySnippetProvider : AbstractSingleChangeSnippetProvider +internal abstract class AbstractPropertySnippetProvider : AbstractSingleChangeSnippetProvider + where TPropertyDeclarationSyntax : SyntaxNode { /// /// Generates the property syntax. /// Requires language specificity for the TypeSyntax as well as the /// type of the PropertySyntax. /// - protected abstract Task GenerateSnippetSyntaxAsync(Document document, int position, CancellationToken cancellationToken); + protected abstract Task GenerateSnippetSyntaxAsync(Document document, int position, CancellationToken cancellationToken); - protected override async Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) + protected sealed override async Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) { var propertyDeclaration = await GenerateSnippetSyntaxAsync(document, position, cancellationToken).ConfigureAwait(false); return new TextChange(TextSpan.FromBounds(position, position), propertyDeclaration.NormalizeWhitespace().ToFullString()); } - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) - { - return syntaxFacts.IsPropertyDeclaration; - } } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSingleChangeSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSingleChangeSnippetProvider.cs index 46cef6359cf9a..68161aa7637b9 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSingleChangeSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSingleChangeSnippetProvider.cs @@ -9,7 +9,8 @@ namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; -internal abstract class AbstractSingleChangeSnippetProvider : AbstractSnippetProvider +internal abstract class AbstractSingleChangeSnippetProvider : AbstractSnippetProvider + where TSnippetSyntax : SyntaxNode { protected abstract Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken); diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index e4f02087b46bb..97d1fb5a2f799 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -20,14 +19,14 @@ namespace Microsoft.CodeAnalysis.Snippets; -internal abstract class AbstractSnippetProvider : ISnippetProvider +internal abstract class AbstractSnippetProvider : ISnippetProvider + where TSnippetSyntax : SyntaxNode { public abstract string Identifier { get; } public abstract string Description { get; } public virtual ImmutableArray AdditionalFilterTexts => []; - protected readonly SyntaxAnnotation CursorAnnotation = new(); protected readonly SyntaxAnnotation FindSnippetAnnotation = new(); /// @@ -44,17 +43,12 @@ internal abstract class AbstractSnippetProvider : ISnippetProvider /// /// Gets the position that we want the caret to be at after all of the indentation/formatting has been done. /// - protected abstract int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText); - - /// - /// Helper function to retrieve the specific type of snippet syntax when it needs to be searched for again. - /// - protected abstract Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts); + protected abstract int GetTargetCaretPosition(TSnippetSyntax caretTarget, SourceText sourceText); /// /// Method to find the locations that must be renamed and where tab stops must be inserted into the snippet. /// - protected abstract ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken); + protected abstract ImmutableArray GetPlaceHolderLocationsList(TSnippetSyntax node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken); /// /// Determines if the location is valid for a snippet, @@ -106,11 +100,7 @@ public async Task GetSnippetAsync(Document document, int position var documentWithIndentation = await AddIndentationToDocumentAsync(reformattedDocument, cancellationToken).ConfigureAwait(false); var reformattedRoot = await documentWithIndentation.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var caretTarget = reformattedRoot.GetAnnotatedNodes(CursorAnnotation).FirstOrDefault(); - var mainChangeNode = reformattedRoot.GetAnnotatedNodes(FindSnippetAnnotation).FirstOrDefault(); - - Contract.ThrowIfNull(caretTarget); - Contract.ThrowIfNull(mainChangeNode); + var mainChangeNode = (TSnippetSyntax)reformattedRoot.GetAnnotatedNodes(FindSnippetAnnotation).First(); var annotatedReformattedDocument = documentWithIndentation.WithSyntaxRoot(reformattedRoot); @@ -129,7 +119,7 @@ public async Task GetSnippetAsync(Document document, int position return new SnippetChange( textChanges: changesArray, - cursorPosition: GetTargetCaretPosition(syntaxFacts, caretTarget, sourceText), + cursorPosition: GetTargetCaretPosition(mainChangeNode, sourceText), placeholders: placeholders); } @@ -186,7 +176,7 @@ private async Task CleanupDocumentAsync( private async Task GetDocumentWithSnippetAndTriviaAsync(Document snippetDocument, int position, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { var root = await snippetDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var nearestStatement = FindAddedSnippetSyntaxNode(root, position, GetSnippetContainerFunction(syntaxFacts)); + var nearestStatement = FindAddedSnippetSyntaxNode(root, position); if (nearestStatement is null) { @@ -216,7 +206,7 @@ private static async Task GetDocumentWithSnippetAsync(Document documen private async Task AddFormatAnnotationAsync(Document document, int position, CancellationToken cancellationToken) { - var annotatedSnippetRoot = await AnnotateNodesToReformatAsync(document, FindSnippetAnnotation, CursorAnnotation, position, cancellationToken).ConfigureAwait(false); + var annotatedSnippetRoot = await AnnotateNodesToReformatAsync(document, position, cancellationToken).ConfigureAwait(false); document = document.WithSyntaxRoot(annotatedSnippetRoot); return document; } @@ -224,37 +214,36 @@ private async Task AddFormatAnnotationAsync(Document document, int pos /// /// Method to added formatting annotations to the created snippet. /// - protected virtual async Task AnnotateNodesToReformatAsync(Document document, - SyntaxAnnotation findSnippetAnnotation, SyntaxAnnotation cursorAnnotation, int position, CancellationToken cancellationToken) + protected virtual async Task AnnotateNodesToReformatAsync( + Document document, int position, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var syntaxFacts = document.GetRequiredLanguageService(); - var snippetExpressionNode = FindAddedSnippetSyntaxNode(root, position, GetSnippetContainerFunction(syntaxFacts)); + var snippetExpressionNode = FindAddedSnippetSyntaxNode(root, position); Contract.ThrowIfNull(snippetExpressionNode); - var reformatSnippetNode = snippetExpressionNode.WithAdditionalAnnotations(findSnippetAnnotation, cursorAnnotation, Simplifier.Annotation, Formatter.Annotation); + var reformatSnippetNode = snippetExpressionNode.WithAdditionalAnnotations(FindSnippetAnnotation, Simplifier.Annotation, Formatter.Annotation); return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - protected virtual SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, Func isCorrectContainer) - { - var closestNode = root.FindNode(TextSpan.FromBounds(position, position), getInnermostNodeForTie: true); - - if (!isCorrectContainer(closestNode)) - { - return null; - } - - return closestNode; - } + protected virtual TSnippetSyntax? FindAddedSnippetSyntaxNode(SyntaxNode root, int position) + => root.FindNode(TextSpan.FromBounds(position, position), getInnermostNodeForTie: true) as TSnippetSyntax; /// /// Certain snippets require more indentation - snippets with blocks. /// The SyntaxGenerator does not insert this space for us nor does the LSP Snippet Expander. /// We need to manually add that spacing to snippets containing blocks. /// - protected virtual async Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) + private async Task AddIndentationToDocumentAsync(Document document, CancellationToken cancellationToken) { - return await Task.FromResult(document).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var snippetNode = root.GetAnnotatedNodes(FindSnippetAnnotation).FirstOrDefault(); + + if (snippetNode is not TSnippetSyntax snippet) + return document; + + return await AddIndentationToDocumentAsync(document, snippet, cancellationToken).ConfigureAwait(false); } + + protected virtual Task AddIndentationToDocumentAsync(Document document, TSnippetSyntax snippet, CancellationToken cancellationToken) + => Task.FromResult(document); } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractStatementSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractStatementSnippetProvider.cs index 32ce9a49d5eaf..16b98b988c8e9 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractStatementSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractStatementSnippetProvider.cs @@ -6,7 +6,8 @@ namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; -internal abstract class AbstractStatementSnippetProvider : AbstractSingleChangeSnippetProvider +internal abstract class AbstractStatementSnippetProvider : AbstractSingleChangeSnippetProvider + where TStatementSyntax : SyntaxNode { protected override bool IsValidSnippetLocation(in SnippetContext context, CancellationToken cancellationToken) => context.SyntaxContext.IsStatementContext || context.SyntaxContext.IsGlobalStatementContext; diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractTypeSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractTypeSnippetProvider.cs index 12f4e2a5f11f0..1a03d3168a221 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractTypeSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractTypeSnippetProvider.cs @@ -12,13 +12,14 @@ namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; -internal abstract class AbstractTypeSnippetProvider : AbstractSnippetProvider +internal abstract class AbstractTypeSnippetProvider : AbstractSnippetProvider + where TTypeDeclarationSyntax : SyntaxNode { - protected abstract void GetTypeDeclarationIdentifier(SyntaxNode node, out SyntaxToken identifier); - protected abstract Task GenerateTypeDeclarationAsync(Document document, int position, CancellationToken cancellationToken); + protected abstract SyntaxToken GetTypeDeclarationIdentifier(TTypeDeclarationSyntax node); + protected abstract Task GenerateTypeDeclarationAsync(Document document, int position, CancellationToken cancellationToken); protected abstract Task GetAccessibilityModifiersChangeAsync(Document document, int position, CancellationToken cancellationToken); - protected override async Task> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken) + protected sealed override async Task> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken) { var typeDeclaration = await GenerateTypeDeclarationAsync(document, position, cancellationToken).ConfigureAwait(false); @@ -33,13 +34,10 @@ protected override async Task> GenerateSnippetTextCha return [mainChange]; } - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected sealed override ImmutableArray GetPlaceHolderLocationsList(TTypeDeclarationSyntax node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); - GetTypeDeclarationIdentifier(node, out var identifier); - arrayBuilder.Add(new SnippetPlaceholder(identifier.ValueText, identifier.SpanStart)); - - return arrayBuilder.ToImmutableArray(); + var identifier = GetTypeDeclarationIdentifier(node); + return [new SnippetPlaceholder(identifier.ValueText, identifier.SpanStart)]; } protected static async Task AreAccessibilityModifiersRequiredAsync(Document document, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractWhileLoopSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractWhileLoopSnippetProvider.cs index a118a52e99764..c1ccba04b71b7 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractWhileLoopSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractWhileLoopSnippetProvider.cs @@ -2,21 +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. -using System; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders; -internal abstract class AbstractWhileLoopSnippetProvider : AbstractConditionalBlockSnippetProvider +internal abstract class AbstractWhileLoopSnippetProvider + : AbstractConditionalBlockSnippetProvider + where TWhileStatementSyntax : SyntaxNode + where TExpressionSyntax : SyntaxNode { - public override string Identifier => "while"; - - public override string Description => FeaturesResources.while_loop; - - protected override Func GetSnippetContainerFunction(ISyntaxFacts syntaxFacts) => syntaxFacts.IsWhileStatement; - - protected override SyntaxNode GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo) - => generator.WhileStatement(inlineExpressionInfo?.Node.WithoutLeadingTrivia() ?? generator.TrueLiteralExpression(), []); + protected sealed override TWhileStatementSyntax GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, InlineExpressionInfo? inlineExpressionInfo) + => (TWhileStatementSyntax)generator.WhileStatement(inlineExpressionInfo?.Node.WithoutLeadingTrivia() ?? generator.TrueLiteralExpression(), []); } diff --git a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs b/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs deleted file mode 100644 index 2e80613840883..0000000000000 --- a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.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; -using Microsoft.CodeAnalysis.Host; - -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. - /// - DocumentId? TryGetActiveDocument(); - - /// - /// Get a read only collection of the s of all the visible documents in the workspace. - /// - ImmutableArray GetVisibleDocuments(); - - event EventHandler ActiveDocumentChanged; - - /// - /// Raised when a text buffer that's not part of a workspace is changed. - /// - event EventHandler NonRoslynBufferTextChanged; -} diff --git a/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckSpanService.cs b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckSpanService.cs index fb4791044b82f..cc00957b66935 100644 --- a/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckSpanService.cs +++ b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckSpanService.cs @@ -37,7 +37,7 @@ ImmutableArray GetSpans() var worker = new Worker(this, syntaxFacts, classifier, virtualCharService, spans); worker.Recurse(root, cancellationToken); - return spans.ToImmutable(); + return spans.ToImmutableAndClear(); } } @@ -68,10 +68,8 @@ public void Recurse(SyntaxNode root, CancellationToken cancellationToken) using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(root); - while (stack.Count > 0) + while (stack.TryPop(out var current)) { - var current = stack.Pop(); - if (current.IsToken) { ProcessToken(current.AsToken(), cancellationToken); @@ -264,9 +262,8 @@ private void ProcessDocComment(SyntaxNode node, CancellationToken cancellationTo using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(node); - while (stack.Count > 0) + while (stack.TryPop(out var current)) { - var current = stack.Pop(); if (current.IsToken) { var token = current.AsToken(); diff --git a/src/Features/Core/Portable/StackTraceExplorer/StackTraceAnalyzer.cs b/src/Features/Core/Portable/StackTraceExplorer/StackTraceAnalyzer.cs index e6f787106845b..8ddf4c63ed6e3 100644 --- a/src/Features/Core/Portable/StackTraceExplorer/StackTraceAnalyzer.cs +++ b/src/Features/Core/Portable/StackTraceExplorer/StackTraceAnalyzer.cs @@ -67,7 +67,7 @@ private static ImmutableArray Parse(string callstack, CancellationT } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private static IEnumerable SplitLines(VirtualCharSequence callstack) diff --git a/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs b/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs index 0591664252acf..523087d5364de 100644 --- a/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs +++ b/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs @@ -106,6 +106,6 @@ private static ImmutableArray GetFileMatches(Solution solution, StackF } } - return potentialMatches.ToImmutableArray(); + return [.. potentialMatches]; } } diff --git a/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs b/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs index 3bc3820da385d..bfbb74ee10ba7 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs @@ -74,11 +74,11 @@ private static BlockStructure GetBlockStructure( private static BlockStructure CreateBlockStructure(in BlockStructureContext context) { - using var _ = ArrayBuilder.GetInstance(context.Spans.Count, out var updatedSpans); + var updatedSpans = new FixedSizeArrayBuilder(context.Spans.Count); foreach (var span in context.Spans) updatedSpans.Add(UpdateBlockSpan(span, context.Options)); - return new BlockStructure(updatedSpans.ToImmutableAndClear()); + return new BlockStructure(updatedSpans.MoveToImmutable()); } private static BlockSpan UpdateBlockSpan(BlockSpan blockSpan, in BlockStructureOptions options) diff --git a/src/Features/Core/Portable/TaskList/AbstractTaskListService.cs b/src/Features/Core/Portable/TaskList/AbstractTaskListService.cs index 304745a969900..4fffe4664dde4 100644 --- a/src/Features/Core/Portable/TaskList/AbstractTaskListService.cs +++ b/src/Features/Core/Portable/TaskList/AbstractTaskListService.cs @@ -73,7 +73,7 @@ private async Task> GetTaskListItemsInProcessAsync( AppendTaskListItems(descriptors, syntaxDoc, trivia, items); } - return items.ToImmutable(); + return items.ToImmutableAndClear(); } private bool ContainsComments(SyntaxTrivia trivia) diff --git a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs index 6b52fabbaec56..92722b2a2f169 100644 --- a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs +++ b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs @@ -121,7 +121,7 @@ internal static ImmutableArray GetUnusedReferences( unusedReferencesBuilder.AddRange(unusedReferences); } - return unusedReferencesBuilder.ToImmutable(); + return unusedReferencesBuilder.ToImmutableAndClear(); } private static ImmutableArray RemoveDirectlyUsedReferences( @@ -177,7 +177,7 @@ private static ImmutableArray RemoveDirectlyUsedReferences( RemoveAllCompilationAssemblies(reference, usedAssemblyFilePaths); } - return unusedReferencesBuilder.ToImmutable(); + return unusedReferencesBuilder.ToImmutableAndClear(); } private static ImmutableArray RemoveTransitivelyUsedReferences( @@ -219,7 +219,7 @@ private static ImmutableArray RemoveTransitivelyUsedReferences( RemoveAllCompilationAssemblies(reference, usedAssemblyFilePaths); } - return unusedReferencesBuilder.ToImmutable(); + return unusedReferencesBuilder.ToImmutableAndClear(); } internal static bool HasAnyCompilationAssembly(ReferenceInfo reference) @@ -256,9 +256,7 @@ internal static ImmutableArray GetAllCompilationAssemblies(ReferenceInfo { var transitiveCompilationAssemblies = reference.Dependencies .SelectMany(dependency => GetAllCompilationAssemblies(dependency)); - return reference.CompilationAssemblies - .Concat(transitiveCompilationAssemblies) - .ToImmutableArray(); + return [.. reference.CompilationAssemblies, .. transitiveCompilationAssemblies]; } public static async Task UpdateReferencesAsync( diff --git a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs index 91ae027a843b9..c730ff32c5eb8 100644 --- a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs +++ b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.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.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; @@ -28,11 +29,16 @@ private class FindReferencesProgress(OperationCollector valueTrackingProgressCol public ValueTask OnDefinitionFoundAsync(SymbolGroup symbolGroup, CancellationToken _) => new(); - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken _) => new(); - - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken _) => new(); + public async ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, + CancellationToken cancellationToken) + { + foreach (var (_, symbol, location) in references) + await OnReferenceFoundAsync(symbol, location, cancellationToken).ConfigureAwait(false); + } - public async ValueTask OnReferenceFoundAsync(SymbolGroup _, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + private async ValueTask OnReferenceFoundAsync( + ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) { if (!location.Location.IsInSource) { diff --git a/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs b/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs index 0720c59837c96..36cc24bc78513 100644 --- a/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs +++ b/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs @@ -33,7 +33,7 @@ public ImmutableArray GetItems() { lock (_lock) { - return _items.ToImmutableArray(); + return [.. _items]; } } diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index 9c47cb263583d..4cb37639c8f1a 100644 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -218,76 +218,5 @@ static string GetGeneratedDocumentPath(string prefix, string relativeDocumentPat private static string GetRelativeDocumentPath(string projectDirectory, string designTimeDocumentFilePath) => PathUtilities.GetRelativePath(projectDirectory, designTimeDocumentFilePath)[..^".g.cs".Length]; - - private static bool HasMatchingFilePath(string designTimeDocumentFilePath, string designTimeProjectDirectory, string compileTimeFilePath) - { - var relativeDocumentPath = GetRelativeDocumentPath(designTimeProjectDirectory, designTimeDocumentFilePath); - - var compileTimeFileName = PathUtilities.GetFileName(compileTimeFilePath, includeExtension: false); - - if (compileTimeFileName.EndsWith(".g", StringComparison.Ordinal)) - compileTimeFileName = compileTimeFileName[..^".g".Length]; - - return compileTimeFileName == GetIdentifierFromPath(relativeDocumentPath); - } - - internal static async Task> GetDesignTimeDocumentsAsync( - Solution compileTimeSolution, - ImmutableArray compileTimeDocumentIds, - Solution designTimeSolution, - CancellationToken cancellationToken, - string? generatedDocumentPathPrefix = null) - { - using var _1 = ArrayBuilder.GetInstance(out var result); - using var _2 = PooledDictionary>.GetInstance(out var compileTimeFilePathsByProject); - - foreach (var compileTimeDocumentId in compileTimeDocumentIds) - { - if (designTimeSolution.ContainsDocument(compileTimeDocumentId)) - { - result.Add(compileTimeDocumentId); - } - else - { - var compileTimeDocument = await compileTimeSolution.GetTextDocumentAsync(compileTimeDocumentId, cancellationToken).ConfigureAwait(false); - var filePath = compileTimeDocument?.State.FilePath; - if (filePath != null && (generatedDocumentPathPrefix != null - ? filePath.StartsWith(generatedDocumentPathPrefix) - : s_razorSourceGeneratorFileNamePrefixes.Any(static (prefix, filePath) => filePath.StartsWith(prefix), filePath))) - { - compileTimeFilePathsByProject.MultiAdd(compileTimeDocumentId.ProjectId, filePath); - } - } - } - - if (result.Count == compileTimeDocumentIds.Length) - { - Debug.Assert(compileTimeFilePathsByProject.Count == 0); - return compileTimeDocumentIds; - } - - foreach (var (projectId, compileTimeFilePaths) in compileTimeFilePathsByProject) - { - var designTimeProjectState = designTimeSolution.GetProjectState(projectId); - if (designTimeProjectState == null) - { - continue; - } - - var designTimeProjectDirectory = PathUtilities.GetDirectoryName(designTimeProjectState.FilePath)!; - - foreach (var (_, designTimeDocumentState) in designTimeProjectState.DocumentStates.States) - { - if (IsRazorDesignTimeDocument(designTimeDocumentState) && - compileTimeFilePaths.Any(compileTimeFilePath => HasMatchingFilePath(designTimeDocumentState.FilePath!, designTimeProjectDirectory, compileTimeFilePath))) - { - result.Add(designTimeDocumentState.Id); - } - } - } - - compileTimeFilePathsByProject.FreeValues(); - return result.ToImmutable(); - } } } diff --git a/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs b/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs index dab7ec7322399..a675497c8f77c 100644 --- a/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs +++ b/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs @@ -71,7 +71,7 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument( hasAllInformation: sourceCodeKind == SourceCodeKind.Script), compilationOptions: compilationOptions, parseOptions: parseOptions, - documents: SpecializedCollections.SingletonEnumerable(documentInfo), + documents: [documentInfo], metadataReferences: metadataReferences); return projectInfo; diff --git a/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs b/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs index 5fdf919e4568d..3416f9566fba8 100644 --- a/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs +++ b/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs @@ -12,6 +12,8 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Precedence; using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Shared.Extensions; + #if DEBUG using System.Diagnostics; #endif @@ -98,7 +100,7 @@ private ImmutableArray GetExpressionsAndOperators( { using var _ = ArrayBuilder.GetInstance(out var result); AddExpressionsAndOperators(precedence, binaryExpr, result); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private void AddExpressionsAndOperators( @@ -109,9 +111,8 @@ private void AddExpressionsAndOperators( var stack = pooledStack.Object; stack.Push(expr); - while (!stack.IsEmpty()) + while (stack.TryPop(out var currentNodeOrToken)) { - var currentNodeOrToken = stack.Pop(); if (currentNodeOrToken.IsNode && IsValidBinaryExpression(precedence, currentNodeOrToken.AsNode())) { _syntaxFacts.GetPartsOfBinaryExpression(currentNodeOrToken.AsNode()!, out var left, out var opToken, out var right); diff --git a/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs b/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs index 9cbcd65cfeeb5..34d657cdc1884 100644 --- a/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs +++ b/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs @@ -115,21 +115,22 @@ private ImmutableArray GetWrapEdits(bool align) } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private ImmutableArray GetUnwrapEdits() { - using var _ = ArrayBuilder.GetInstance(out var result); + var count = _exprsAndOperators.Length - 1; + var result = new FixedSizeArrayBuilder(count); - for (var i = 0; i < _exprsAndOperators.Length - 1; i++) + for (var i = 0; i < count; i++) { result.Add(Edit.UpdateBetween( _exprsAndOperators[i], SingleWhitespaceTrivia, NoTrivia, _exprsAndOperators[i + 1])); } - return result.ToImmutable(); + return result.MoveToImmutable(); } } } diff --git a/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs b/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs index e6e6743f9622e..0b551a0774901 100644 --- a/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs +++ b/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Wrapping.ChainedExpression; @@ -132,7 +133,7 @@ private ImmutableArray> GetChainChunks(SyntaxN using var _2 = ArrayBuilder>.GetInstance(out var chunks); BreakPiecesIntoChunks(pieces, chunks); - return chunks.ToImmutable(); + return chunks.ToImmutableAndClear(); } private void BreakPiecesIntoChunks( @@ -230,11 +231,11 @@ pieces[index] is var piece && private static ImmutableArray GetSubRange( ArrayBuilder pieces, int start, int end) { - using var _ = ArrayBuilder.GetInstance(end - start, out var result); + var result = new FixedSizeArrayBuilder(end - start); for (var i = start; i < end; i++) result.Add(pieces[i]); - return result.ToImmutableAndClear(); + return result.MoveToImmutable(); } private bool IsDecomposableChainPart(SyntaxNode? node) @@ -272,38 +273,32 @@ private void Decompose(SyntaxNode node, ArrayBuilder pieces) if (node is null) return; - var stack = SharedPools.Default>().AllocateAndClear(); + using var pooledStack = SharedPools.Default>().GetPooledObject(); + var stack = pooledStack.Object; stack.Push(node); - try + + while (stack.TryPop(out var nodeOrToken)) { - while (stack.Count > 0) + if (nodeOrToken.IsToken) { - var nodeOrToken = stack.Pop(); - if (nodeOrToken.IsToken) - { - // tokens can't be decomposed. just add to the result list. - pieces.Add(nodeOrToken.AsToken()); - continue; - } - - var currentNode = nodeOrToken.AsNode()!; - if (!IsDecomposableChainPart(currentNode)) - { - // We've hit some node that can't be decomposed further (like an argument list, or name node). - // Just add directly to the pieces list. - pieces.Add(currentNode); - continue; - } + // tokens can't be decomposed. just add to the result list. + pieces.Add(nodeOrToken.AsToken()); + continue; + } - // Hit something that can be decomposed. Push it onto the stack in reverse so that we continue to - // traverse the node from right to left as we pop things off the end of the stack. - foreach (var child in currentNode.ChildNodesAndTokens().Reverse()) - stack.Push(child); + var currentNode = nodeOrToken.AsNode()!; + if (!IsDecomposableChainPart(currentNode)) + { + // We've hit some node that can't be decomposed further (like an argument list, or name node). + // Just add directly to the pieces list. + pieces.Add(currentNode); + continue; } - } - finally - { - SharedPools.Default>().Free(stack); + + // Hit something that can be decomposed. Push it onto the stack in reverse so that we continue to + // traverse the node from right to left as we pop things off the end of the stack. + foreach (var child in currentNode.ChildNodesAndTokens().Reverse()) + stack.Push(child); } } } diff --git a/src/Features/Core/Portable/Wrapping/ChainedExpression/ChainedExpressionCodeActionComputer.cs b/src/Features/Core/Portable/Wrapping/ChainedExpression/ChainedExpressionCodeActionComputer.cs index 50579e88e56c2..a31c0171fa21c 100644 --- a/src/Features/Core/Portable/Wrapping/ChainedExpression/ChainedExpressionCodeActionComputer.cs +++ b/src/Features/Core/Portable/Wrapping/ChainedExpression/ChainedExpressionCodeActionComputer.cs @@ -163,7 +163,7 @@ private ImmutableArray GetWrapEdits(int wrappingColumn, bool align) position += NormalizedWidth(chunk); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static int NormalizedWidth(ImmutableArray chunk) @@ -178,7 +178,7 @@ private ImmutableArray GetUnwrapEdits() var flattened = _chunks.SelectManyAsArray(c => c); DeleteAllSpacesInChunk(result, flattened); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static void DeleteAllSpacesInChunk( diff --git a/src/Features/Core/Portable/Wrapping/SeparatedSyntaxList/SeparatedSyntaxListCodeActionComputer.cs b/src/Features/Core/Portable/Wrapping/SeparatedSyntaxList/SeparatedSyntaxListCodeActionComputer.cs index 40c4321579d54..a9d2659ee3c21 100644 --- a/src/Features/Core/Portable/Wrapping/SeparatedSyntaxList/SeparatedSyntaxListCodeActionComputer.cs +++ b/src/Features/Core/Portable/Wrapping/SeparatedSyntaxList/SeparatedSyntaxListCodeActionComputer.cs @@ -217,7 +217,7 @@ private ImmutableArray GetUnwrapAllEdits(WrappingStyle wrappingStyle) if (last.IsNode) result.Add(Edit.DeleteBetween(last, _listSyntax.GetLastToken())); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } #endregion @@ -342,7 +342,7 @@ private ImmutableArray GetWrapLongLinesEdits( result.Add(Edit.DeleteBetween(itemsAndSeparators.Last(), _listSyntax.GetLastToken())); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } #endregion @@ -445,7 +445,7 @@ private ImmutableArray GetWrapEachEdits( result.Add(Edit.DeleteBetween(itemsAndSeparators.Last(), _listSyntax.GetLastToken())); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } #endregion diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 63516e7fefb3f..23dc84fb88357 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -322,7 +322,7 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Built-in Copilot analysis - Built-in Copilot analysis + Integrovaná analýza Copilot @@ -657,7 +657,7 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Dismiss - Dismiss + Zavřít @@ -1155,6 +1155,16 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Metoda {0} musí vrátit stream, který podporuje operace čtení a hledání. + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files Různé soubory @@ -1385,6 +1395,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Před kvantifikátorem {x,y} není nic uvedeno. This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group odkaz na nedefinovanou skupinu @@ -2620,6 +2635,26 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv Výběr není obsažený v typu. + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent Tichý @@ -2680,6 +2715,11 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv V cestě sestavení {0} byl nalezen symbol. + + Symbols + Symbols + + Syntax error Chyba syntaxe @@ -2710,6 +2750,11 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv Sestavení analyzátoru {0} odkazuje na verzi {1} kompilátoru, která je novější než aktuálně spuštěná verze {2}. + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. Výběr obsahuje volání lokální funkce, aniž by byla deklarovaná. @@ -2765,6 +2810,11 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv Není možné přečíst zdrojový soubor {0} nebo soubor PDB sestavený pro projekt, který tyto soubory obsahuje. Případné změny provedené v tomto souboru během ladění se nepoužijí, dokud se jeho obsah nebude shodovat se sestaveným zdrojem. + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property Neznámá vlastnost diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 62bad2305aebc..45a6398c4cf84 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -322,7 +322,7 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Built-in Copilot analysis - Built-in Copilot analysis + Integrierte Copilot-Analyse @@ -657,7 +657,7 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Dismiss - Dismiss + Schließen @@ -1155,6 +1155,16 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d "{0}" muss einen Datenstrom zurückgeben, der Lese- und Suchvorgänge unterstützt. + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files Sonstige Dateien @@ -1385,6 +1395,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Quantifizierer {x,y} nach nichts. This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group Verweis auf nicht definierte Gruppe @@ -2620,6 +2635,26 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Die Auswahl ist nicht in keinem Typ enthalten. + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent Still @@ -2680,6 +2715,11 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Das Symbol wurde unter dem Assemblypfad „{0}“ gefunden + + Symbols + Symbols + + Syntax error Syntaxfehler @@ -2710,6 +2750,11 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Die Analyzerassembly „{0}“ verweist auf Version „{1}“ des Compilers, die neuer ist als die aktuell ausgeführte Version „{2}“. + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. Die Auswahl enthält einen lokalen Funktionsaufruf ohne Deklaration. @@ -2765,6 +2810,11 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Die Quelldatei "{0}" oder die für das enthaltende Projekt kompilierte PDB-Datei kann nicht gelesen werden. Alle Änderungen, die während des Debuggens an dieser Datei vorgenommen wurden, werden erst angewendet, wenn der Inhalt dem kompilierten Quellcode entspricht. + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property Unbekannte Eigenschaft diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 73775a7ad045e..71531aa60b319 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -322,7 +322,7 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Built-in Copilot analysis - Built-in Copilot analysis + Análisis integrado de Copilot @@ -372,7 +372,7 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa TODO - TODO + TODO "TODO" is an indication that there is work still to be done. @@ -657,7 +657,7 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Dismiss - Dismiss + Descartar @@ -1155,6 +1155,16 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa {0} debe devolver una secuencia que admita operaciones de lectura y búsqueda. + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files Archivos varios @@ -1385,6 +1395,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Cuantificador {x,y} después de nada This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group referencia al grupo no definido @@ -2620,6 +2635,26 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us La selección no está contenida dentro de un tipo. + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent Silencioso @@ -2680,6 +2715,11 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us Símbolo encontrado en la ruta de acceso del ensamblado '{0}' + + Symbols + Symbols + + Syntax error Error de sintaxis @@ -2710,6 +2750,11 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us El ensamblado del analizador ”{0}” hace referencia a la versión ”{1}” del compilador, que es más reciente que la versión "{2}" que se está ejecutando actualmente. + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. La selección contiene una llamada a una función local sin la declaración correspondiente. @@ -2765,6 +2810,11 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us No se puede leer el archivo de código fuente "{0}" o el PDB compilado para el proyecto que lo contiene. Los cambios realizados en este archivo durante la depuración no se aplicarán hasta que su contenido coincida con el del código fuente compilado. + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property Propiedad desconocida diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index b079dfae22f47..cd2b70bc32a42 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -322,7 +322,7 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Built-in Copilot analysis - Built-in Copilot analysis + Analyse Copilot intégrée @@ -657,7 +657,7 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Dismiss - Dismiss + Ignorer @@ -1155,6 +1155,16 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai {0} doit retourner un flux qui prend en charge les opérations de lecture et de recherche. + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files Fichiers divers @@ -1385,6 +1395,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Le quantificateur {x,y} ne suit rien This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group référence à un groupe indéfini @@ -2053,7 +2068,7 @@ Vous pouvez concaténer au moins deux plages de caractères. Par exemple, pour s The regular expression construct \P{ name } matches any character that does not belong to a Unicode general category or named block, where name is the category abbreviation or named block name. - La construction d'expression régulière \P{ nom } correspond aux caractères qui n'appartiennent pas à une catégorie générale Unicode ou à un bloc nommé, nom étant l'abréviation de la catégorie ou le nom du bloc nommé. + La construction d'expression régulière \P{ name } correspond aux caractères qui n'appartiennent pas à une catégorie générale Unicode ou à un bloc nommé, nom étant l'abréviation de la catégorie ou le nom du bloc nommé. @@ -2345,7 +2360,7 @@ Il existe une ambiguïté entre les codes d'échappement octaux (par exemple \16 The regular expression construct \p{ name } matches any character that belongs to a Unicode general category or named block, where name is the category abbreviation or named block name. - La construction d'expression régulière \p{ nom } correspond aux caractères qui appartiennent à une catégorie générale Unicode ou à un bloc nommé, nom étant l'abréviation de la catégorie ou le nom du bloc nommé. + La construction d'expression régulière \p{ name } correspond aux caractères qui appartiennent à une catégorie générale Unicode ou à un bloc nommé, nom étant l'abréviation de la catégorie ou le nom du bloc nommé. @@ -2620,6 +2635,26 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée Sélection non contenue dans un type. + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent Silencieux @@ -2680,6 +2715,11 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée Symbole trouvé dans le chemin d’accès de l’assembly '{0}' + + Symbols + Symbols + + Syntax error Erreur de syntaxe @@ -2710,6 +2750,11 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée L'assembly d'analyseur '{0}' fait référence à la version '{1}' du compilateur, qui est plus récente que la version en cours d'exécution '{2}'. + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. La sélection contient un appel de fonction locale sans sa déclaration. @@ -2765,6 +2810,11 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée Impossible de lire le fichier source '{0}' ou le PDB généré pour le projet conteneur. Les changements apportés à ce fichier durant le débogage ne seront pas appliqués tant que son contenu ne correspondra pas à la source générée. + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property Propriété inconnue diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index d3865a828ff24..4bb62acc56dbf 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -322,7 +322,7 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Built-in Copilot analysis - Built-in Copilot analysis + Analisi Copilot predefinita @@ -657,7 +657,7 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Dismiss - Dismiss + Chiudi @@ -1155,6 +1155,16 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa {0} deve restituire un flusso che supporta operazioni di lettura e ricerca. + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files File esterni @@ -1385,6 +1395,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Il quantificatore {x,y} non segue alcun elemento This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group riferimento a gruppo indefinito @@ -2620,6 +2635,26 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' La selezione non è contenuta all'interno di un tipo. + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent Invisibile @@ -2680,6 +2715,11 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' Simbolo trovato nel percorso assembly '{0}' + + Symbols + Symbols + + Syntax error Errore di sintassi @@ -2710,6 +2750,11 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' L'assembly dell'analizzatore '{0}' fa riferimento alla versione '{1}' del compilatore, che è più recente della versione attualmente in esecuzione '{2}'. + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. La selezione contiene una chiamata di funzione locale senza la relativa dichiarazione. @@ -2765,6 +2810,11 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' Non è possibile leggere il file di origine '{0}' o il file PDB compilato per il progetto contenitore. Tutte le modifiche apportate a questo file durante il debug non verranno applicate finché il relativo contenuto non corrisponde al codice sorgente compilato. + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property Proprietà sconosciuta diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 5f9b178ff8c9c..614bf87b47d2b 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -322,7 +322,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Built-in Copilot analysis - Built-in Copilot analysis + 組み込みの Copilot の分析 @@ -657,7 +657,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Dismiss - Dismiss + 無視 @@ -1155,6 +1155,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0} は、読み取りとシーク操作をサポートするストリームを返す必要があります。 + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files その他のファイル @@ -1385,6 +1395,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 量指定子 {x,y} の前に何もありません This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group 未定義のグループへの参照 @@ -2620,6 +2635,26 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 選択が型の内部に含まれていません。 + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent サイレント @@ -2680,6 +2715,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of アセンブリ パス '{0}' にシンボルが見つかりました + + Symbols + Symbols + + Syntax error 構文エラー @@ -2710,6 +2750,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of アナライザー アセンブリ '{0}' は、コンパイラのバージョン '{1}' を参照しています。これは、現在実行中のバージョン '{2}' よりも新しいバージョンです。 + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. 選択範囲に、宣言のないローカル関数呼び出しが含まれています。 @@ -2765,6 +2810,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of ソース ファイル '{0}'、またはそれを含むプロジェクト用にビルドされた PDB を読み取れません。デバッグ中にこのファイルに加えられた変更は、そのコンテンツがビルドされたソースと一致するまで適用されません。 + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property 不明なプロパティ diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 7412d68328430..375f752f611ab 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -322,7 +322,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Built-in Copilot analysis - Built-in Copilot analysis + 기본 제공 Copilot 분석 @@ -657,7 +657,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Dismiss - Dismiss + 해제 @@ -1155,6 +1155,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0}은(는) 읽기 및 검색 작업을 지원하는 스트림을 반환해야 합니다. + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files 기타 파일 @@ -1385,6 +1395,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 수량자 {x,y} 앞에 아무 것도 없습니다. This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group 정의되지 않은 그룹에 대한 참조 @@ -2620,6 +2635,26 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 선택 항목이 형식 내에 포함되어 있지 않습니다. + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent 침묵 @@ -2680,6 +2715,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 어셈블리 경로 '{0}'에서 기호를 찾음 + + Symbols + Symbols + + Syntax error 구문 오류 @@ -2710,6 +2750,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 분석기 어셈블리 '{0}'은(는) 컴파일러의 '{1}' 버전을 참조하며, 이 버전은 현재 실행 중인 버전 '{2}'보다 최신 버전입니다. + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. 선언이 없는 로컬 함수 호출이 선택 영역에 있습니다. @@ -2765,6 +2810,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 소스 파일 '{0}' 또는 포함하는 프로젝트에 대해 빌드된 PDB를 읽을 수 없습니다. 디버그하는 동안 이 파일의 변경된 모든 내용은 해당 콘텐츠가 빌드된 소스와 일치할 때까지 적용되지 않습니다. + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property 알 수 없는 속성 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 062a32c6cf5d7..b0ef2b9c0c2c7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -322,7 +322,7 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Built-in Copilot analysis - Built-in Copilot analysis + Wbudowana analiza funkcji Copilot @@ -657,7 +657,7 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Dismiss - Dismiss + Odrzuć @@ -1155,6 +1155,16 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k {0} musi zwrócić strumień, który obsługuje operacje odczytu i wyszukiwania. + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files Różne pliki @@ -1385,6 +1395,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Nic nie występuje przed kwantyfikatorem {x,y} This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group odwołanie do niezdefiniowanej grupy @@ -2053,7 +2068,7 @@ Można połączyć dwa lub więcej zakresów znaków. Na przykład aby określi The regular expression construct \P{ name } matches any character that does not belong to a Unicode general category or named block, where name is the category abbreviation or named block name. - Konstrukcja wyrażenia regularnego \P{ nazwa } pasuje do dowolnego znaku, który nie należy do kategorii ogólnej znaków Unicode ani do bloku nazwanego, gdzie „nazwa” to skrót kategorii lub nazwa bloku nazwanego. + Konstrukcja wyrażenia regularnego \P{ name } pasuje do dowolnego znaku, który nie należy do kategorii ogólnej znaków Unicode ani do bloku nazwanego, gdzie „nazwa” to skrót kategorii lub nazwa bloku nazwanego. @@ -2345,7 +2360,7 @@ Istnieje niejednoznaczność między ósemkowymi kodami ucieczki (takimi jak \16 The regular expression construct \p{ name } matches any character that belongs to a Unicode general category or named block, where name is the category abbreviation or named block name. - Konstrukcja wyrażenia regularnego \p{ nazwa } pasuje do dowolnego znaku należącego do ogólnej kategorii Unicode lub bloku nazwanego, gdzie „nazwa” to skrót kategorii lub nazwa bloku nazwanego. + Konstrukcja wyrażenia regularnego \p{ name } pasuje do dowolnego znaku należącego do ogólnej kategorii Unicode lub bloku nazwanego, gdzie „nazwa” to skrót kategorii lub nazwa bloku nazwanego. @@ -2620,6 +2635,26 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Zaznaczenie nie jest uwzględnione w typie. + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent Cichy @@ -2680,6 +2715,11 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Znaleziono symbol w ścieżce zestawu „{0}” + + Symbols + Symbols + + Syntax error Błąd składniowy @@ -2710,6 +2750,11 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Zestaw analizatora „{0}” odwołuje się do wersji „{1}” kompilatora, która jest nowsza niż obecnie uruchomiona wersja „{2}”. + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. Zaznaczenie zawiera wywołanie funkcji lokalnej bez jego deklaracji. @@ -2765,6 +2810,11 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Nie można odczytać pliku źródłowego „{0}” lub pliku PDB skompilowanego dla projektu, w którym jest on zawarty. Wszystkie zmiany wprowadzone w tym pliku podczas debugowania nie zostaną zastosowane do czasu, aż jego zawartość będzie zgodna ze skompilowanym źródłem. + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property Nieznana właściwość diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 8ad5b710974a7..2a0f5278d86fd 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -322,7 +322,7 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Built-in Copilot analysis - Built-in Copilot analysis + Análise interna do Copilot @@ -372,7 +372,7 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess TODO - TODO + TODO "TODO" is an indication that there is work still to be done. @@ -657,7 +657,7 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Dismiss - Dismiss + Ignorar @@ -1155,6 +1155,16 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess {0} precisa retornar um fluxo compatível com as operações de leitura e busca. + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files Arquivos Diversos @@ -1385,6 +1395,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Nada precede o quantificador {x,y} This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group referência a grupo indefinido @@ -2620,6 +2635,26 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas Seleção não contida dentro de um tipo. + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent Silencioso @@ -2680,6 +2715,11 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas Símbolo encontrado no caminho de montagem '{0}' + + Symbols + Symbols + + Syntax error Erro de sintaxe @@ -2687,12 +2727,12 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas TODO: free unmanaged resources (unmanaged objects) and override finalizer - TODO: free unmanaged resources (unmanaged objects) and override finalizer + TODO: liberar recursos não gerenciados (objetos não gerenciados) e substituir o finalizador TODO: override finalizer only if '{0}' has code to free unmanaged resources - TODO: override finalizer only if '{0}' has code to free unmanaged resources + TODO: substituir o finalizador somente se '{0}' tiver o código para liberar recursos não gerenciados @@ -2710,6 +2750,11 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas O assembly do analisador '{0}' referencia a versão '{1}' do compilador, que é mais recente que a versão em execução no momento '{2}'. + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. A seleção contém uma chamada de função local sem sua declaração. @@ -2765,6 +2810,11 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas Não é possível ler o arquivo de origem '{0}' ou o PDB criado para o projeto que o contém. Todas as alterações feitas neste arquivo durante a depuração não serão aplicadas até que o conteúdo corresponda ao código-fonte compilado. + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property Propriedade desconhecida @@ -4237,12 +4287,12 @@ Deseja continuar? TODO: dispose managed state (managed objects) - TODO: dispose managed state (managed objects) + TODO: descartar o estado gerenciado (objetos gerenciados) TODO: set large fields to null - TODO: set large fields to null + TODO: definir campos grandes como nulos diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 5b4ac56bd6adb..5a5d439d0e939 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -322,7 +322,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Built-in Copilot analysis - Built-in Copilot analysis + Встроенный анализ Copilot @@ -372,7 +372,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma TODO - TODO + TODO "TODO" is an indication that there is work still to be done. @@ -657,7 +657,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Dismiss - Dismiss + Отклонить @@ -1155,6 +1155,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0} должен возвращать поток, который поддерживает операции чтения и поиска. + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files Прочие файлы @@ -1385,6 +1395,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Отсутствуют элементы перед квантификатором {x,y} This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group Ссылка на неопределенную группу @@ -2053,7 +2068,7 @@ Two or more character ranges can be concatenated. For example, to specify the ra The regular expression construct \P{ name } matches any character that does not belong to a Unicode general category or named block, where name is the category abbreviation or named block name. - Конструкция регулярного выражения \P{ имя } соответствует любому символу, который не относится к общей категории Юникода или именованному блоку, где "имя" — это сокращение названия категории или имя именованного блока. + Конструкция регулярного выражения \P{ name } соответствует любому символу, который не относится к общей категории Юникода или именованному блоку, где "имя" — это сокращение названия категории или имя именованного блока. @@ -2345,7 +2360,7 @@ There is an ambiguity between octal escape codes (such as \16) and \number backr The regular expression construct \p{ name } matches any character that belongs to a Unicode general category or named block, where name is the category abbreviation or named block name. - Конструкция регулярного выражения \p{ имя } соответствует любому символу, который относится к общей категории Юникода или именованному блоку, где "имя" — это сокращение названия категории или имя именованного блока. + Конструкция регулярного выражения \p{ name } соответствует любому символу, который относится к общей категории Юникода или именованному блоку, где "имя" — это сокращение названия категории или имя именованного блока. @@ -2620,6 +2635,26 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Выделенный фрагмент не входит в тип. + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent Тихий @@ -2680,6 +2715,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Найден символ в пути сборки "{0}" + + Symbols + Symbols + + Syntax error Синтаксическая ошибка @@ -2710,6 +2750,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Сборка анализатора "{0}" ссылается на версию "{1}" компилятора, являющуюся более новой, чем версия "{2}". + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. Выделенный фрагмент содержит вызов локальной функции без ее объявления. @@ -2765,6 +2810,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Не удалось считать исходный файл "{0}" или PDB, созданный для содержащего проекта. Все изменения, внесенные в этот файл во время отладки, не будут применены, пока содержимое файла не будет соответствовать созданному источнику. + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property Неизвестное свойство diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 967adb2829322..c6ed18ace5570 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -322,7 +322,7 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Built-in Copilot analysis - Built-in Copilot analysis + Yerleşik Copilot analizi @@ -657,7 +657,7 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Dismiss - Dismiss + Kapat @@ -1155,6 +1155,16 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be {0} okuma ve arama işlemlerini destekleyen bir akış döndürmelidir. + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files Diğer Dosyalar @@ -1385,6 +1395,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Niceleyici {x,y} hiçbir şeyi takip etmiyor This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group tanımsız grubuna başvuru @@ -2620,6 +2635,26 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri Seçim bir türün içinde yer almıyor. + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent Sessiz @@ -2680,6 +2715,11 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri “{0}” derleme yolunda sembol bulundu + + Symbols + Symbols + + Syntax error Söz dizimi hatası @@ -2710,6 +2750,11 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri '{0}' çözümleyici bütünleştirilmiş kodu, derleyicinin şu anda çalışan '{2}' sürümünden daha yeni olan '{1}' sürümüne başvurur. + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. Seçim, bildirimi olmadan bir yerel işlev çağrısı içeriyor. @@ -2765,6 +2810,11 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri '{0}' kaynak dosyası veya içeren proje için oluşturulan PDB okunamıyor. Hata ayıklama sırasında bu dosyada yapılan değişiklikler, dosyanın içeriği oluşturulan kaynakla eşleşene kadar uygulanmaz. + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property Bilinmeyen Özellik diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 0a8cc24393276..9853290a52d4e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -322,7 +322,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Built-in Copilot analysis - Built-in Copilot analysis + 内置 Copilot 分析 @@ -657,7 +657,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Dismiss - Dismiss + 消除 @@ -1155,6 +1155,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0} 必须返回支持读取和查找操作的流。 + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files 杂项文件 @@ -1385,6 +1395,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 限定符 {x,y} 前没有任何内容 This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group 对未定义组的引用 @@ -2053,7 +2068,7 @@ Two or more character ranges can be concatenated. For example, to specify the ra The regular expression construct \P{ name } matches any character that does not belong to a Unicode general category or named block, where name is the category abbreviation or named block name. - 正则表达式构造 \P{name} 匹配不属于 Unicode 通用类别或命名块的任何字符,其中 "name" 是类别缩写或命名块名称。 + 正则表达式构造 \P{ name } 匹配不属于 Unicode 通用类别或命名块的任何字符,其中 "name" 是类别缩写或命名块名称。 @@ -2345,7 +2360,7 @@ There is an ambiguity between octal escape codes (such as \16) and \number backr The regular expression construct \p{ name } matches any character that belongs to a Unicode general category or named block, where name is the category abbreviation or named block name. - 正则表达式构造 \p{name} 匹配属于 Unicode 通用类别或命名块的任何字符,其中 "name" 是类别缩写或命名块名称。 + 正则表达式构造 \p{ name } 匹配属于 Unicode 通用类别或命名块的任何字符,其中 "name" 是类别缩写或命名块名称。 @@ -2620,6 +2635,26 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 所选内容不包含在类型内。 + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent 无提示 @@ -2680,6 +2715,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 在程序集路径 "{0}" 中找到符号 + + Symbols + Symbols + + Syntax error 语法错误 @@ -2710,6 +2750,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 分析器程序集“{0}”引用了编译器的版本“{1}”,该版本高于当前正在运行的版本“{2}”。 + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. 所选内容包含不带声明的本地函数调用。 @@ -2765,6 +2810,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 无法读取源文件 "{0}" 或为包含项目生成的 PDB。在调试期间对此文件所做的任何更改都不会应用,直到其内容与生成的源匹配为止。 + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property 未知属性 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index f5da4b37f6d34..824278aac6021 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -322,7 +322,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Built-in Copilot analysis - Built-in Copilot analysis + 內建 Copilot 分析 @@ -657,7 +657,7 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Dismiss - Dismiss + 關閉 @@ -1155,6 +1155,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0} 必須傳回支援讀取和搜尋作業的資料流。 + + Method '{0}' must be static and non-generic + Method '{0}' must be static and non-generic + + + + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + Method '{0}' must have a single parameter of type '{1}' and return '{2}' + + Miscellaneous Files 其他檔案 @@ -1385,6 +1395,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 數量詞 {x,y} 前面沒有任何項目 This is an error message shown to the user when they write an invalid Regular Expression. Example: * + + Query + Query + + reference to undefined group 對未定義群組的參考 @@ -2620,6 +2635,26 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 選取範圍未包含在類型內。 + + Semantic Search + Semantic Search + + + + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). + + + + Semantic search query failed to compile + Semantic search query failed to compile + + + + Semantic search query terminated with exception + Semantic search query terminated with exception + + Silent 靜音 @@ -2680,6 +2715,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 在組件路徑 '{0}' 中找到符號 + + Symbols + Symbols + + Syntax error 語法錯誤 @@ -2710,6 +2750,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 分析程式組件 '{0}' 參考編譯器的版本 '{1}' ,比目前執行的版本 '{2}' 還要新。 + + The query does not specify '{0}' method or top-level function + The query does not specify '{0}' method or top-level function + + The selection contains a local function call without its declaration. 選取範圍包含區域函式呼叫,但不含其宣告。 @@ -2765,6 +2810,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 無法讀取來源檔案 '{0}' 或為包含該檔案之專案所建置的 PDB。等到此檔案的內容與已建置的來源一致後,才會套用於偵錯期間對此檔案所做的所有變更。 + + Unable to load type '{0}': '{1}' + Unable to load type '{0}': '{1}' + + Unknown property 未知屬性 diff --git a/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1.cs b/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1.cs index 549a0b00746e9..0e27129adc0bc 100644 --- a/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1.cs +++ b/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1.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.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; @@ -13,19 +14,27 @@ public static partial class CSharpCodeRefactoringVerifier where TCodeRefactoring : CodeRefactoringProvider, new() { /// - public static Task VerifyRefactoringAsync(string source, string fixedSource) + public static Task VerifyRefactoringAsync( + [StringSyntax("C#-Test")] string source) + { + return VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, source); + } + + /// + public static Task VerifyRefactoringAsync( + [StringSyntax("C#-Test")] string source, [StringSyntax("C#-Test")] string fixedSource) { return VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); } /// - public static Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource) + public static Task VerifyRefactoringAsync([StringSyntax("C#-Test")] string source, DiagnosticResult expected, [StringSyntax("C#-Test")] string fixedSource) { return VerifyRefactoringAsync(source, [expected], fixedSource); } /// - public static Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource) + public static Task VerifyRefactoringAsync([StringSyntax("C#-Test")] string source, DiagnosticResult[] expected, [StringSyntax("C#-Test")] string fixedSource) { var test = new Test { diff --git a/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs b/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs index 30b0185c73e23..b0bdf4fd1c942 100644 --- a/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs +++ b/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs @@ -22,13 +22,11 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; +using Microsoft.CodeAnalysis.Diagnostics.EngineV2; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; @@ -156,9 +154,7 @@ private protected virtual IDocumentServiceProvider GetDocumentServiceProvider() protected virtual TestComposition GetComposition() => FeaturesTestCompositions.Features - .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)) - .AddAssemblies(typeof(DiagnosticService).Assembly); + .AddAssemblies(typeof(DiagnosticIncrementalAnalyzer).Assembly); protected virtual void InitializeWorkspace(TestWorkspace workspace, TestParameters parameters) { diff --git a/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionTest_NoEditor.cs b/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionTest_NoEditor.cs index 6d88ecb9ef500..9b506aada4ead 100644 --- a/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionTest_NoEditor.cs +++ b/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionTest_NoEditor.cs @@ -105,7 +105,7 @@ internal async Task GetCodeRefactoringAsync( { var provider = CreateCodeRefactoringProvider(workspace, parameters); - var actions = ArrayBuilder<(CodeAction, TextSpan?)>.GetInstance(); + using var _ = ArrayBuilder<(CodeAction, TextSpan?)>.GetInstance(out var actions); var codeActionOptionsProvider = parameters.globalOptions?.IsEmpty() == false ? CodeActionOptionsStorage.GetCodeActionOptionsProvider(workspace.GlobalOptions) @@ -114,7 +114,6 @@ internal async Task GetCodeRefactoringAsync( var context = new CodeRefactoringContext(document, selectedOrAnnotatedSpan, (a, t) => actions.Add((a, t)), codeActionOptionsProvider, CancellationToken.None); await provider.ComputeRefactoringsAsync(context); var result = actions.Count > 0 ? new CodeRefactoring(provider, actions.ToImmutable(), FixAllProviderInfo.Create(provider), codeActionOptionsProvider) : null; - actions.Free(); return result; } diff --git a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractSuppressionAllCodeTests.cs b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractSuppressionAllCodeTests.cs index a606dbdd9fbe8..84304048a6662 100644 --- a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractSuppressionAllCodeTests.cs +++ b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractSuppressionAllCodeTests.cs @@ -88,7 +88,7 @@ protected async Task TestPragmaOrAttributeAsync( continue; } - var fixes = fixer.GetFixesAsync(document, diagnostic.Location.SourceSpan, SpecializedCollections.SingletonEnumerable(diagnostic), CodeActionOptions.DefaultProvider, CancellationToken.None).GetAwaiter().GetResult(); + var fixes = fixer.GetFixesAsync(document, diagnostic.Location.SourceSpan, [diagnostic], CodeActionOptions.DefaultProvider, CancellationToken.None).GetAwaiter().GetResult(); if (fixes == null || fixes.Count() <= 0) { continue; diff --git a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_NoEditor.cs b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_NoEditor.cs index 8248844d15a95..d19e283a4238c 100644 --- a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_NoEditor.cs +++ b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_NoEditor.cs @@ -216,7 +216,7 @@ private static FixAllState GetFixAllState( if (scope == FixAllScope.Custom) { // Bulk fixing diagnostics in selected scope. - var diagnosticsToFix = ImmutableDictionary.CreateRange(SpecializedCollections.SingletonEnumerable(KeyValuePairUtil.Create(document, diagnostics.ToImmutableArray()))); + var diagnosticsToFix = ImmutableDictionary.CreateRange([KeyValuePairUtil.Create(document, diagnostics.ToImmutableArray())]); return FixAllState.Create(fixAllProvider, diagnosticsToFix, fixer, equivalenceKey, optionsProvider); } diff --git a/src/Features/DiagnosticsTestUtilities/Utilities/StringSyntaxAttribute.cs b/src/Features/DiagnosticsTestUtilities/Utilities/StringSyntaxAttribute.cs new file mode 100644 index 0000000000000..4c54aa7019884 --- /dev/null +++ b/src/Features/DiagnosticsTestUtilities/Utilities/StringSyntaxAttribute.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. + +#if !NET + +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] + public sealed class StringSyntaxAttribute : Attribute + { + public StringSyntaxAttribute(string syntax) => Syntax = syntax; + public string Syntax { get; } + } +} + +#endif diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs index c7b1b6b940865..4be80e009e63a 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs @@ -67,7 +67,7 @@ public async Task CreatingDirectoryWatchRequestsDirectoryWatch() var watcher = GetSingleFileWatcher(dynamicCapabilitiesRpcTarget); - Assert.Equal(tempDirectory.Path + Path.DirectorySeparatorChar, watcher.GlobPattern.BaseUri.LocalPath); + Assert.Equal(tempDirectory.Path, watcher.GlobPattern.BaseUri.LocalPath); Assert.Equal("**/*", watcher.GlobPattern.Pattern); // Get rid of the registration and it should be gone again diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs index 25f175456269d..18ae7694a345c 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs @@ -20,6 +20,9 @@ private async Task CreateReporterAsync() { var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync(TestOutputLogger.Factory, includeDevKitComponents: true, out var _); + // VS Telemetry requires this environment variable to be set. + Environment.SetEnvironmentVariable("CommonPropertyBagPath", Path.GetTempFileName()); + var reporter = exportProvider.GetExport().Value; Assert.NotNull(reporter); diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/ServiceBrokerFactory.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/ServiceBrokerFactory.cs index aa3787f934d57..8ad3f78c6dac2 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/ServiceBrokerFactory.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/ServiceBrokerFactory.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.Immutable; using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis.BrokeredServices; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Composition; @@ -30,13 +32,16 @@ internal class ServiceBrokerFactory private readonly ExportProvider _exportProvider; private Task _bridgeCompletionTask; private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly ImmutableArray _onServiceBrokerInitialized; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public ServiceBrokerFactory(ExportProvider exportProvider) + public ServiceBrokerFactory([ImportMany] IEnumerable onServiceBrokerInitialized, + ExportProvider exportProvider) { _exportProvider = exportProvider; _bridgeCompletionTask = Task.CompletedTask; + _onServiceBrokerInitialized = onServiceBrokerInitialized.ToImmutableArray(); } /// @@ -64,6 +69,17 @@ public async Task CreateAsync() Contract.ThrowIfFalse(_container == null, "We should only create one container."); _container = await BrokeredServiceContainer.CreateAsync(_exportProvider, _cancellationTokenSource.Token); + + foreach (var onInitialized in _onServiceBrokerInitialized) + { + try + { + onInitialized.OnServiceBrokerInitialized(_container.GetFullAccessServiceBroker()); + } + catch (Exception) + { + } + } } public async Task CreateAndConnectAsync(string brokeredServicePipeName) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs index bd87c4f2b5155..4fa57508b1b38 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs @@ -43,6 +43,11 @@ internal class Descriptors { RemoteProjectInitializationStatusService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, { BrokeredServiceDescriptors.SolutionSnapshotProvider.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, { BrokeredServiceDescriptors.DebuggerManagedHotReloadService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, + { BrokeredServiceDescriptors.HotReloadSessionNotificationService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, + { BrokeredServiceDescriptors.ManagedHotReloadAgentManagerService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, + { BrokeredServiceDescriptors.HotReloadOptionService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, + { BrokeredServiceDescriptors.HotReloadLoggerService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, + { BrokeredServiceDescriptors.MauiLaunchCustomizerService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, }.ToImmutableDictionary(); public static ServiceJsonRpcDescriptor CreateDescriptor(ServiceMoniker serviceMoniker) => new( diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs index a6e3d506bf119..254707da542a7 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Runtime.InteropServices; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.LanguageServer; using Microsoft.CodeAnalysis.ProjectSystem; @@ -41,7 +42,7 @@ public static bool SupportsLanguageServerHost(LanguageServerHost languageServerH public IFileChangeContext CreateContext(params WatchedDirectory[] watchedDirectories) { - return new FileChangeContext(watchedDirectories.ToImmutableArray(), this); + return new FileChangeContext([.. watchedDirectories], this); } private class FileChangeContext : IFileChangeContext @@ -64,7 +65,9 @@ private class FileChangeContext : IFileChangeContext /// The list of file paths we're watching manually that were outside the directories being watched. The count in this case counts /// the number of /// - private readonly Dictionary _watchedFiles = new Dictionary(StringComparer.Ordinal); + private readonly Dictionary _watchedFiles = new Dictionary(s_stringComparer); + private static readonly StringComparer s_stringComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + private static readonly StringComparison s_stringComparison = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; public FileChangeContext(ImmutableArray watchedDirectories, LspFileChangeWatcher lspFileChangeWatcher) { @@ -78,7 +81,7 @@ public FileChangeContext(ImmutableArray watchedDirectories, Ls { GlobPattern = new RelativePattern { - BaseUri = ProtocolConversions.CreateAbsoluteUri(d.Path), + BaseUri = ProtocolConversions.CreateRelativePatternBaseUri(d.Path), Pattern = d.ExtensionFilter is not null ? "**/*" + d.ExtensionFilter : "**/*" } }).ToArray(); @@ -97,7 +100,7 @@ private void WatchedFilesHandler_OnNotificationRaised(object? sender, DidChangeW // Unfortunately the LSP protocol doesn't give us any hint of which of the file watches we might have sent to the client // was the one that registered for this change, so we have to check paths to see if this one we should respond to. - if (WatchedDirectory.FilePathCoveredByWatchedDirectories(_watchedDirectories, filePath, StringComparison.Ordinal)) + if (WatchedDirectory.FilePathCoveredByWatchedDirectories(_watchedDirectories, filePath, s_stringComparison)) { FileChanged?.Invoke(this, filePath); } @@ -126,7 +129,7 @@ public void Dispose() public IWatchedFile EnqueueWatchingFile(string filePath) { // If we already have this file under our path, we may not have to do additional watching - if (WatchedDirectory.FilePathCoveredByWatchedDirectories(_watchedDirectories, filePath, StringComparison.OrdinalIgnoreCase)) + if (WatchedDirectory.FilePathCoveredByWatchedDirectories(_watchedDirectories, filePath, s_stringComparison)) return NoOpWatchedFile.Instance; // Record that we're now watching this file diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs index 5b21bd69e817a..3f901a8be52b8 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs @@ -19,7 +19,7 @@ internal sealed class SimpleFileChangeWatcher : IFileChangeWatcher { public IFileChangeContext CreateContext(params WatchedDirectory[] watchedDirectories) { - return new FileChangeContext(watchedDirectories.ToImmutableArray()); + return new FileChangeContext([.. watchedDirectories]); } private class FileChangeContext : IFileChangeContext diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs index b178281cb3bed..f68f4a7e7d536 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs @@ -185,7 +185,7 @@ private async ValueTask LoadOrReloadProjectsAsync(ImmutableSegmentedList project Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through."); var languageServerManager = LanguageServerHost.Instance.GetRequiredLspService(); - var unresolvedParams = new UnresolvedDependenciesParams(projectPaths.ToArray()); + var unresolvedParams = new UnresolvedDependenciesParams([.. projectPaths]); await languageServerManager.SendRequestAsync(ProjectNeedsRestoreName, unresolvedParams, cancellationToken); } diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectSystemDiagnosticSource.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectSystemDiagnosticSource.cs index 57a11e0431565..5fc406f39698a 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectSystemDiagnosticSource.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectSystemDiagnosticSource.cs @@ -6,17 +6,12 @@ using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; -internal class ProjectSystemDiagnosticSource : IProjectSystemDiagnosticSource -{ - public void ClearAllDiagnosticsForProject(ProjectId projectId) - { - } - public void ClearAnalyzerReferenceDiagnostics(AnalyzerFileReference fileReference, string language, ProjectId projectId) - { - } +internal sealed class ProjectSystemDiagnosticSource : IProjectSystemDiagnosticSource +{ + public static readonly ProjectSystemDiagnosticSource Instance = new(); - public void ClearDiagnosticsForProject(ProjectId projectId, object key) + private ProjectSystemDiagnosticSource() { } @@ -24,9 +19,4 @@ public DiagnosticData CreateAnalyzerLoadFailureDiagnostic(AnalyzerLoadFailureEve { return DocumentAnalysisExecutor.CreateAnalyzerLoadFailureDiagnostic(e, fullPath, projectId, language); } - - public void UpdateDiagnosticsForProject(ProjectId projectId, object key, IEnumerable items) - { - // TODO: actually store the diagnostics - } } diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/VSCodeAnalyzerLoader.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/VSCodeAnalyzerLoader.cs index 81ad2a04ee55d..d62627d2742e4 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/VSCodeAnalyzerLoader.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/VSCodeAnalyzerLoader.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. -using System.Collections.Immutable; using System.Composition; using System.Reflection; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Services; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.Extensions.Logging; namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; @@ -17,20 +13,16 @@ namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; [Export(typeof(VSCodeAnalyzerLoader)), Shared] internal class VSCodeAnalyzerLoader { - private readonly IDiagnosticAnalyzerService _analyzerService; - private readonly DiagnosticService _diagnosticService; - [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VSCodeAnalyzerLoader(IDiagnosticAnalyzerService analyzerService, IDiagnosticService diagnosticService) + public VSCodeAnalyzerLoader() { - _analyzerService = analyzerService; - _diagnosticService = (DiagnosticService)diagnosticService; } +#pragma warning disable CA1822 // Mark members as static public void InitializeDiagnosticsServices() +#pragma warning restore CA1822 // Mark members as static { - _diagnosticService.Register((IDiagnosticUpdateSource)_analyzerService); } public static IAnalyzerAssemblyLoader CreateAnalyzerAssemblyLoader(ExtensionAssemblyManager extensionAssemblyManager, ILogger logger) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs index 353aeb665de82..b6142d715962b 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs @@ -40,7 +40,7 @@ public async Task AddAdditionalFilesAsync(IReadOnlyList addition await using var _ = disposableBatchScope.ConfigureAwait(false); foreach (var additionalFile in additionalFiles) - _project.AddAdditionalFile(additionalFile.FilePath, folders: additionalFile.FolderNames.ToImmutableArray()); + _project.AddAdditionalFile(additionalFile.FilePath, folders: [.. additionalFile.FolderNames]); } public async Task AddAnalyzerConfigFilesAsync(IReadOnlyList analyzerConfigPaths, CancellationToken cancellationToken) @@ -85,7 +85,7 @@ public async Task AddSourceFilesAsync(IReadOnlyList sourceFiles, await using var _ = disposableBatchScope.ConfigureAwait(false); foreach (var sourceFile in sourceFiles) - _project.AddSourceFile(sourceFile.FilePath, folders: sourceFile.FolderNames.ToImmutableArray()); + _project.AddSourceFile(sourceFile.FilePath, folders: [.. sourceFile.FolderNames]); } public void Dispose() @@ -190,7 +190,7 @@ public async Task SetCommandLineArgumentsAsync(IReadOnlyList arguments, var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); await using var _ = disposableBatchScope.ConfigureAwait(false); - _optionsProcessor.SetCommandLine(arguments.ToImmutableArray()); + _optionsProcessor.SetCommandLine([.. arguments]); } public async Task SetDisplayNameAsync(string displayName, CancellationToken cancellationToken) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs index 9f91e4a0a2525..7f57a870a5cec 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs @@ -87,7 +87,7 @@ private static ImmutableArray GetRestorePaths(RestoreParams request, Sol { if (request.ProjectFilePaths.Any()) { - return request.ProjectFilePaths.ToImmutableArray(); + return [.. request.ProjectFilePaths]; } // No file paths were specified - this means we should restore all projects in the solution. diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/LanguageServerHost.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/LanguageServerHost.cs index b3681184fb566..013a155b73eb5 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/LanguageServerHost.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/LanguageServerHost.cs @@ -28,7 +28,8 @@ internal sealed class LanguageServerHost public LanguageServerHost(Stream inputStream, Stream outputStream, ExportProvider exportProvider, ILogger logger) { - var handler = new HeaderDelimitedMessageHandler(outputStream, inputStream, new JsonMessageFormatter()); + var messageFormatter = new JsonMessageFormatter(); + var handler = new HeaderDelimitedMessageHandler(outputStream, inputStream, messageFormatter); // If there is a jsonrpc disconnect or server shutdown, that is handled by the AbstractLanguageServer. No need to do anything here. _jsonRpc = new JsonRpc(handler) @@ -43,7 +44,7 @@ public LanguageServerHost(Stream inputStream, Stream outputStream, ExportProvide var lspLogger = new LspServiceLogger(_logger); var hostServices = exportProvider.GetExportedValue().HostServices; - _roslynLanguageServer = roslynLspFactory.Create(_jsonRpc, capabilitiesProvider, WellKnownLspServerKinds.CSharpVisualBasicLspServer, lspLogger, hostServices); + _roslynLanguageServer = roslynLspFactory.Create(_jsonRpc, messageFormatter.JsonSerializer, capabilitiesProvider, WellKnownLspServerKinds.CSharpVisualBasicLspServer, lspLogger, hostServices); } public void Start() diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj index b628f743b2ed0..fa42a2d113562 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj @@ -71,6 +71,7 @@ + diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs index 69df0964d0b8f..a166543886f52 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs @@ -52,7 +52,7 @@ public void HandleRawMessage(string rawMessage) public ImmutableArray GetTestCases() { Contract.ThrowIfFalse(_isComplete, "Tried to get test cases before discovery completed"); - return _testCases.ToImmutableArray(); + return [.. _testCases]; } public bool IsAborted() diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.cs index 62bfe8514d51c..7237daa8f6cb9 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.cs @@ -57,7 +57,7 @@ public async Task> DiscoverTestsAsync( var stopwatch = SharedStopwatch.StartNew(); // The async APIs for vs test are broken (current impl ends up just hanging), so we must use the sync API instead. - var discoveryTask = Task.Run(() => vsTestConsoleWrapper.DiscoverTests(SpecializedCollections.SingletonEnumerable(projectOutputPath), discoverySettings: runSettings, discoveryHandler), cancellationToken); + var discoveryTask = Task.Run(() => vsTestConsoleWrapper.DiscoverTests([projectOutputPath], discoverySettings: runSettings, discoveryHandler), cancellationToken); cancellationToken.Register(() => vsTestConsoleWrapper.CancelDiscovery()); await discoveryTask; @@ -105,6 +105,6 @@ private async Task> MatchDiscoveredTestsToTestsInRangeA } _logger.LogDebug($"Filtered {discoveredTests.Length} to {matchedTests.Count} tests"); - return matchedTests.ToImmutable(); + return matchedTests.ToImmutableAndClear(); } } diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.cs.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.cs.xlf index fce49dc95612a..ca6673bf876c9 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.cs.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.cs.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + Vyvolaná výjimka: {0} @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + Čtení souboru .runsettings na {0} se nezdařilo:{1} diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.de.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.de.xlf index 606734d5d3b4f..61878d5f160a9 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.de.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.de.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + Ausgelöste Ausnahme: „{0}“ @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + Fehler beim Lesen der RUNSETTINGS-Datei unter {0}:{1} diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.es.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.es.xlf index 130e824fefda5..ffbbb2b8bca28 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.es.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.es.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + Se produjo una excepción: {0} @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + No se pudo leer el archivo .runsettings en {0}:{1} diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.fr.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.fr.xlf index 79ac14bee0805..6941c95ef6fb4 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.fr.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.fr.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + Exception levée : « {0} » @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + Échec de la lecture du fichier .runsettings à l'adresse {0} :{1} diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.it.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.it.xlf index e28f18705f9cd..27e970862bbae 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.it.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.it.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + È stata generata un'eccezione: {0} @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + Non è stato possibile leggere il file con estensione runsettings in {0}:{1} diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ja.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ja.xlf index 18ea96ef8d519..c4940eb7c664d 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ja.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ja.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + 例外がスローされました: {0} @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + {0}:{1} の .runsettings ファイルを読み取れできませんでした diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ko.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ko.xlf index 546715d7d11a6..56ecb0074946d 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ko.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ko.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + 예외 throw됨: {0} @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + {0}:{1}에서 .runsettings 파일을 읽지 못했습니다. diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pl.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pl.xlf index 93450a5eb8081..6bb6fb61ba444 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pl.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pl.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + Zgłoszony wyjątek: {0} @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + Nie można odczytać pliku .runsettings w {0}:{1} diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pt-BR.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pt-BR.xlf index 810426026e0fa..0be751a2cb52d 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pt-BR.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pt-BR.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + Exceção gerada: {0} @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + Falha ao ler o arquivo .runsettings em {0}:{1} diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ru.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ru.xlf index ffb1d27e0ec3b..9f6ffe157bbd1 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ru.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ru.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + Возникло исключение: "{0}" @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + Не удалось прочитать файл RUNSETTINGS в {0}:{1} diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.tr.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.tr.xlf index c96b2c883235f..a57b21bf3164d 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.tr.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.tr.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + Özel durum oluştu: {0} @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + Şu konumdaki .runsettings dosyası okunamadı: {0}:{1} diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hans.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hans.xlf index 4ae377a65b07b..9c0e452b77054 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hans.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hans.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + 引发了异常: {0} @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + 无法读取 {0}:{1}的 .runsettings 文件 diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hant.xlf b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hant.xlf index 88046acb63b22..b3ce5e2fdaaec 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hant.xlf +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hant.xlf @@ -44,7 +44,7 @@ Exception thrown: {0} - Exception thrown: {0} + 擲回的例外狀況: {0} @@ -59,7 +59,7 @@ Failed to read .runsettings file at {0}:{1} - Failed to read .runsettings file at {0}:{1} + 無法讀取位於 {0} 的 .runsettings 檔案: {1} diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj deleted file mode 100644 index 482f6dfbdeba7..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - - Library - netstandard2.0 - Microsoft.CommonLanguageServerProtocol.Framework - - - false - - BINARY_COMPAT - - - - - - - diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs index 7a602999dfb3b..4d6774ecd3ab3 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs @@ -5,6 +5,7 @@ using System; using Microsoft.CommonLanguageServerProtocol.Framework.Handlers; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; using Roslyn.LanguageServer.Protocol; using StreamJsonRpc; @@ -14,7 +15,7 @@ internal class ExampleLanguageServer : AbstractLanguageServer? _addExtraHandlers; - public ExampleLanguageServer(JsonRpc jsonRpc, ILspLogger logger, Action? addExtraHandlers) : base(jsonRpc, logger) + public ExampleLanguageServer(JsonRpc jsonRpc, JsonSerializer jsonSerializer, ILspLogger logger, Action? addExtraHandlers) : base(jsonRpc, jsonSerializer, logger) { _addExtraHandlers = addExtraHandlers; // This spins up the queue and ensure the LSP is ready to start receiving requests @@ -26,7 +27,7 @@ protected override ILspServices ConstructLspServices() var serviceCollection = new ServiceCollection(); var _ = AddHandlers(serviceCollection) - .AddSingleton(_logger) + .AddSingleton(Logger) .AddSingleton, ExampleRequestContextFactory>() .AddSingleton(s => HandlerProvider) .AddSingleton, CapabilitiesManager>() diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/RequestExecutionQueueTests.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/RequestExecutionQueueTests.cs index 21adf768ace4c..1766f5eeebebc 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/RequestExecutionQueueTests.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/RequestExecutionQueueTests.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Nerdbank.Streams; +using Newtonsoft.Json; using StreamJsonRpc; using Xunit; @@ -15,7 +16,7 @@ public class RequestExecutionQueueTests { private class MockServer : AbstractLanguageServer { - public MockServer() : base(new JsonRpc(new HeaderDelimitedMessageHandler(FullDuplexStream.CreatePair().Item1)), NoOpLspLogger.Instance) + public MockServer() : base(new JsonRpc(new HeaderDelimitedMessageHandler(FullDuplexStream.CreatePair().Item1)), JsonSerializer.CreateDefault(), NoOpLspLogger.Instance) { } @@ -50,7 +51,7 @@ public async Task ExecuteAsync_ThrowCompletes() var lspServices = GetLspServices(); // Act & Assert - await Assert.ThrowsAsync(() => requestExecutionQueue.ExecuteAsync(1, ThrowingHandler.Name, lspServices, CancellationToken.None)); + await Assert.ThrowsAsync(() => requestExecutionQueue.ExecuteAsync(1, ThrowingHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None)); } [Fact] @@ -71,12 +72,12 @@ public async Task ExecuteAsync_WithCancelInProgressWork_CancelsInProgressWorkWhe var cancellingRequestCancellationToken = new CancellationToken(); var completingRequestCancellationToken = new CancellationToken(); - var _ = requestExecutionQueue.ExecuteAsync(1, CancellingHandler.Name, lspServices, cancellingRequestCancellationToken); - var _1 = requestExecutionQueue.ExecuteAsync(1, CompletingHandler.Name, lspServices, completingRequestCancellationToken); + var _ = requestExecutionQueue.ExecuteAsync(1, CancellingHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, cancellingRequestCancellationToken); + var _1 = requestExecutionQueue.ExecuteAsync(1, CompletingHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, completingRequestCancellationToken); // Act & Assert // A Debug.Assert would throw if the tasks hadn't completed when the mutating request is called. - await requestExecutionQueue.ExecuteAsync(1, MutatingHandler.Name, lspServices, CancellationToken.None); + await requestExecutionQueue.ExecuteAsync(1, MutatingHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None); } } @@ -99,7 +100,7 @@ public async Task ExecuteAsync_CompletesTask() var requestExecutionQueue = GetRequestExecutionQueue(false, (TestMethodHandler.Metadata, TestMethodHandler.Instance)); var lspServices = GetLspServices(); - var response = await requestExecutionQueue.ExecuteAsync(request: 1, TestMethodHandler.Name, lspServices, CancellationToken.None); + var response = await requestExecutionQueue.ExecuteAsync(request: 1, TestMethodHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None); Assert.Equal("stuff", response); } @@ -109,7 +110,7 @@ public async Task ExecuteAsync_CompletesTask_Parameterless() var requestExecutionQueue = GetRequestExecutionQueue(false, (TestParameterlessMethodHandler.Metadata, TestParameterlessMethodHandler.Instance)); var lspServices = GetLspServices(); - var response = await requestExecutionQueue.ExecuteAsync(request: NoValue.Instance, TestParameterlessMethodHandler.Name, lspServices, CancellationToken.None); + var response = await requestExecutionQueue.ExecuteAsync(request: NoValue.Instance, TestParameterlessMethodHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None); Assert.True(response); } @@ -119,7 +120,7 @@ public async Task ExecuteAsync_CompletesTask_Notification() var requestExecutionQueue = GetRequestExecutionQueue(false, (TestNotificationHandler.Metadata, TestNotificationHandler.Instance)); var lspServices = GetLspServices(); - var response = await requestExecutionQueue.ExecuteAsync(request: true, TestNotificationHandler.Name, lspServices, CancellationToken.None); + var response = await requestExecutionQueue.ExecuteAsync(request: true, TestNotificationHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None); Assert.Same(NoValue.Instance, response); } @@ -129,7 +130,7 @@ public async Task ExecuteAsync_CompletesTask_Notification_Parameterless() var requestExecutionQueue = GetRequestExecutionQueue(false, (TestParameterlessNotificationHandler.Metadata, TestParameterlessNotificationHandler.Instance)); var lspServices = GetLspServices(); - var response = await requestExecutionQueue.ExecuteAsync(request: NoValue.Instance, TestParameterlessNotificationHandler.Name, lspServices, CancellationToken.None); + var response = await requestExecutionQueue.ExecuteAsync(request: NoValue.Instance, TestParameterlessNotificationHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None); Assert.Same(NoValue.Instance, response); } @@ -140,8 +141,8 @@ public async Task Queue_DrainsOnShutdown() var request = 1; var lspServices = GetLspServices(); - var task1 = requestExecutionQueue.ExecuteAsync(request, TestMethodHandler.Name, lspServices, CancellationToken.None); - var task2 = requestExecutionQueue.ExecuteAsync(request, TestMethodHandler.Name, lspServices, CancellationToken.None); + var task1 = requestExecutionQueue.ExecuteAsync(request, TestMethodHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None); + var task2 = requestExecutionQueue.ExecuteAsync(request, TestMethodHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None); await requestExecutionQueue.DisposeAsync(); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/TestExampleLanguageServer.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/TestExampleLanguageServer.cs index 9d3b3b8734d3c..8754366f4f30a 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/TestExampleLanguageServer.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/TestExampleLanguageServer.cs @@ -9,6 +9,7 @@ using Microsoft.CommonLanguageServerProtocol.Framework.Example; using Microsoft.Extensions.DependencyInjection; using Nerdbank.Streams; +using Newtonsoft.Json; using Roslyn.LanguageServer.Protocol; using StreamJsonRpc; @@ -18,7 +19,8 @@ internal class TestExampleLanguageServer : ExampleLanguageServer { private readonly JsonRpc _clientRpc; - public TestExampleLanguageServer(Stream clientSteam, JsonRpc jsonRpc, ILspLogger logger, Action? addExtraHandlers) : base(jsonRpc, logger, addExtraHandlers) + public TestExampleLanguageServer(Stream clientSteam, JsonRpc jsonRpc, JsonSerializer jsonSerializer, ILspLogger logger, Action? addExtraHandlers) + : base(jsonRpc, jsonSerializer, logger, addExtraHandlers) { _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientSteam, clientSteam, CreateJsonMessageFormatter())) { @@ -111,14 +113,15 @@ internal static TestExampleLanguageServer CreateBadLanguageServer(ILspLogger log { var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(serverStream, serverStream, CreateJsonMessageFormatter())); + var messageFormatter = CreateJsonMessageFormatter(); + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(serverStream, serverStream, messageFormatter)); var extraHandlers = (IServiceCollection serviceCollection) => { serviceCollection.AddSingleton(); }; - var server = new TestExampleLanguageServer(clientStream, jsonRpc, logger, extraHandlers); + var server = new TestExampleLanguageServer(clientStream, jsonRpc, messageFormatter.JsonSerializer, logger, extraHandlers); jsonRpc.StartListening(); server.InitializeTest(); @@ -129,9 +132,10 @@ internal static TestExampleLanguageServer CreateLanguageServer(ILspLogger logger { var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(serverStream, serverStream, CreateJsonMessageFormatter())); + var messageFormatter = CreateJsonMessageFormatter(); + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(serverStream, serverStream, messageFormatter)); - var server = new TestExampleLanguageServer(clientStream, jsonRpc, logger, addExtraHandlers: null); + var server = new TestExampleLanguageServer(clientStream, jsonRpc, messageFormatter.JsonSerializer, logger, addExtraHandlers: null); jsonRpc.StartListening(); server.InitializeTest(); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs index 4af41c4bd96bc..7e36101ec4d64 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs @@ -13,11 +13,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// Manages handler discovery and distribution. /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractHandlerProvider -#else internal abstract class AbstractHandlerProvider -#endif { /// /// Gets the s for all registered methods. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs index 4260996c47ff4..8dddabcf31dbe 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs @@ -6,24 +6,24 @@ #nullable enable using System; +using System.Collections.Frozen; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using StreamJsonRpc; namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractLanguageServer -#else internal abstract class AbstractLanguageServer -#endif { private readonly JsonRpc _jsonRpc; -#pragma warning disable IDE1006 // Naming Styles - Required for API compat, TODO - https://github.com/dotnet/roslyn/issues/72251 - protected readonly ILspLogger _logger; -#pragma warning restore IDE1006 // Naming Styles + protected readonly ILspLogger Logger; + + protected readonly JsonSerializer _jsonSerializer; /// /// These are lazy to allow implementations to define custom variables that are used by @@ -58,11 +58,13 @@ internal abstract class AbstractLanguageServer protected AbstractLanguageServer( JsonRpc jsonRpc, + JsonSerializer jsonSerializer, ILspLogger logger) { - _logger = logger; - + Logger = logger; _jsonRpc = jsonRpc; + _jsonSerializer = jsonSerializer; + _jsonRpc.AddLocalRpcTarget(this); _jsonRpc.Disconnected += JsonRpc_Disconnected; _lspServices = new Lazy(() => ConstructLspServices()); @@ -85,60 +87,68 @@ public void Initialize() /// This should only be called once, and then cached. protected abstract ILspServices ConstructLspServices(); - [Obsolete($"Use {nameof(HandlerProvider)} property instead.", error: false)] - protected virtual IHandlerProvider GetHandlerProvider() - { - var lspServices = _lspServices.Value; - var handlerProvider = new HandlerProvider(lspServices); - SetupRequestDispatcher(handlerProvider); - - return handlerProvider; - } - protected virtual AbstractHandlerProvider HandlerProvider { get { -#pragma warning disable CS0618 // Type or member is obsolete - var handlerProvider = GetHandlerProvider(); -#pragma warning restore CS0618 // Type or member is obsolete - if (handlerProvider is AbstractHandlerProvider abstractHandlerProvider) - { - return abstractHandlerProvider; - } - - return new WrappedHandlerProvider(handlerProvider); + var lspServices = _lspServices.Value; + var handlerProvider = new HandlerProvider(lspServices); + SetupRequestDispatcher(handlerProvider); + return handlerProvider; } } public ILspServices GetLspServices() => _lspServices.Value; - protected virtual void SetupRequestDispatcher(IHandlerProvider handlerProvider) + protected virtual void SetupRequestDispatcher(AbstractHandlerProvider handlerProvider) { + var entryPointMethodInfo = typeof(DelegatingEntryPoint).GetMethod(nameof(DelegatingEntryPoint.ExecuteRequestAsync))!; // Get unique set of methods from the handler provider for the default language. - foreach (var metadata in handlerProvider + foreach (var methodGroup in handlerProvider .GetRegisteredMethods() - .Select(m => new RequestHandlerMetadata(m.MethodName, m.RequestType, m.ResponseType, LanguageServerConstants.DefaultLanguageName)) - .Distinct()) + .GroupBy(m => m.MethodName)) { // Instead of concretely defining methods for each LSP method, we instead dynamically construct the // generic method info from the exported handler types. This allows us to define multiple handlers for - // the same method but different type parameters. This is a key functionality to support TS external - // access as we do not want to couple our LSP protocol version dll to theirs. - // - // We also do not use the StreamJsonRpc support for JToken as the rpc method parameters because we want - // StreamJsonRpc to do the deserialization to handle streaming requests using IProgress. - - var method = DelegatingEntryPoint.GetMethodInstantiation(metadata.RequestType, metadata.ResponseType); + // the same method but different type parameters. This is a key functionality to support LSP extensibility + // in cases like XAML, TS to allow them to use different LSP type definitions + + // Verify that we are not mixing different numbers of request parameters and responses between different language handlers + // e.g. it is not allowed to have a method have both a parameterless and regular parameter handler. + var requestTypes = methodGroup.Select(m => m.RequestType); + var responseTypes = methodGroup.Select(m => m.ResponseType); + if (!AllTypesMatch(requestTypes)) + { + throw new InvalidOperationException($"Language specific handlers for {methodGroup.Key} have mis-matched number of parameters:{Environment.NewLine}{string.Join(Environment.NewLine, methodGroup)}"); + } - var delegatingEntryPoint = new DelegatingEntryPoint(metadata.MethodName, this); + if (!AllTypesMatch(responseTypes)) + { + throw new InvalidOperationException($"Language specific handlers for {methodGroup.Key} have mis-matched number of returns:{Environment.NewLine}{string.Join(Environment.NewLine, methodGroup)}"); + } - var methodAttribute = new JsonRpcMethodAttribute(metadata.MethodName) + var delegatingEntryPoint = new DelegatingEntryPoint(methodGroup.Key, this, methodGroup); + var methodAttribute = new JsonRpcMethodAttribute(methodGroup.Key) { UseSingleObjectParameterDeserialization = true, }; - _jsonRpc.AddLocalRpcMethod(method, delegatingEntryPoint, methodAttribute); + _jsonRpc.AddLocalRpcMethod(entryPointMethodInfo, delegatingEntryPoint, methodAttribute); + } + + static bool AllTypesMatch(IEnumerable types) + { + if (types.All(r => r is null)) + { + return true; + } + + if (types.All(r => r is not null)) + { + return true; + } + + return false; } } @@ -156,7 +166,7 @@ public virtual void OnInitialized() protected virtual IRequestExecutionQueue ConstructRequestExecutionQueue() { var handlerProvider = HandlerProvider; - var queue = new RequestExecutionQueue(this, _logger, handlerProvider); + var queue = new RequestExecutionQueue(this, Logger, handlerProvider); queue.Start(); @@ -168,69 +178,94 @@ protected IRequestExecutionQueue GetRequestExecutionQueue() return _queue.Value; } - /// - /// Wrapper class to hold the method and properties from the - /// that the method info passed to StreamJsonRpc is created from. - /// + protected virtual string GetLanguageForRequest(string methodName, JToken? parameters) + { + Logger.LogInformation($"Using default language handler for {methodName}"); + return LanguageServerConstants.DefaultLanguageName; + } + private sealed class DelegatingEntryPoint { private readonly string _method; + private readonly Lazy> _languageEntryPoint; private readonly AbstractLanguageServer _target; - private static readonly MethodInfo s_entryPointMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(EntryPointAsync))!; - private static readonly MethodInfo s_parameterlessEntryPointMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(ParameterlessEntryPointAsync))!; - private static readonly MethodInfo s_notificationMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(NotificationEntryPointAsync))!; - private static readonly MethodInfo s_parameterlessNotificationMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(ParameterlessNotificationEntryPointAsync))!; + private static readonly MethodInfo s_queueExecuteAsyncMethod = typeof(RequestExecutionQueue).GetMethod(nameof(RequestExecutionQueue.ExecuteAsync))!; - public DelegatingEntryPoint(string method, AbstractLanguageServer target) + public DelegatingEntryPoint(string method, AbstractLanguageServer target, IGrouping handlersForMethod) { _method = method; _target = target; - } - - public static MethodInfo GetMethodInstantiation(Type? requestType, Type? responseType) - => (requestType, responseType) switch + _languageEntryPoint = new Lazy>(() => { - (requestType: not null, responseType: not null) => s_entryPointMethod.MakeGenericMethod(requestType, responseType), - (requestType: null, responseType: not null) => s_parameterlessEntryPointMethod.MakeGenericMethod(responseType), - (requestType: not null, responseType: null) => s_notificationMethod.MakeGenericMethod(requestType), - (requestType: null, responseType: null) => s_parameterlessNotificationMethod, - }; - - public async Task NotificationEntryPointAsync(TRequest request, CancellationToken cancellationToken) where TRequest : class - { - var queue = _target.GetRequestExecutionQueue(); - var lspServices = _target.GetLspServices(); - - _ = await queue.ExecuteAsync(request, _method, lspServices, cancellationToken).ConfigureAwait(false); + var handlerEntryPoints = new Dictionary(); + foreach (var metadata in handlersForMethod) + { + var requestType = metadata.RequestType ?? NoValue.Instance.GetType(); + var responseType = metadata.ResponseType ?? NoValue.Instance.GetType(); + var methodInfo = s_queueExecuteAsyncMethod.MakeGenericMethod(requestType, responseType); + handlerEntryPoints[metadata.Language] = (methodInfo, metadata); + } + + return handlerEntryPoints.ToFrozenDictionary(); + }); } - public async Task ParameterlessNotificationEntryPointAsync(CancellationToken cancellationToken) + /// + /// StreamJsonRpc entry point for all handler methods. + /// The optional parameters allow StreamJsonRpc to call into the same method for any kind of request / notification (with any number of params or response). + /// + public async Task ExecuteRequestAsync(JToken? request = null, CancellationToken cancellationToken = default) { var queue = _target.GetRequestExecutionQueue(); var lspServices = _target.GetLspServices(); - _ = await queue.ExecuteAsync(NoValue.Instance, _method, lspServices, cancellationToken).ConfigureAwait(false); - } + // Retrieve the language of the request so we know how to deserialize it. + var language = _target.GetLanguageForRequest(_method, request); - public async Task EntryPointAsync(TRequest request, CancellationToken cancellationToken) where TRequest : class - { - var queue = _target.GetRequestExecutionQueue(); - var lspServices = _target.GetLspServices(); + // Find the correct request and response types for the given request and language. + if (!_languageEntryPoint.Value.TryGetValue(language, out var requestInfo) + && !_languageEntryPoint.Value.TryGetValue(LanguageServerConstants.DefaultLanguageName, out requestInfo)) + { + throw new InvalidOperationException($"No default or language specific handler was found for {_method} and document with language {language}"); + } + + // Deserialize the request parameters (if any). + var requestObject = DeserializeRequest(request, requestInfo.Metadata, _target._jsonSerializer); - var result = await queue.ExecuteAsync(request, _method, lspServices, cancellationToken).ConfigureAwait(false); + var task = requestInfo.MethodInfo.Invoke(queue, [requestObject, _method, language, lspServices, cancellationToken]) as Task + ?? throw new InvalidOperationException($"Queue result task cannot be null"); + await task.ConfigureAwait(false); + var resultProperty = task.GetType().GetProperty("Result") ?? throw new InvalidOperationException("Result property on task cannot be null"); + var result = resultProperty.GetValue(task); + if (result is null || result == NoValue.Instance) + { + return null; + } - return result; + return JToken.FromObject(result, _target._jsonSerializer); } - public async Task ParameterlessEntryPointAsync(CancellationToken cancellationToken) + private static object DeserializeRequest(JToken? request, RequestHandlerMetadata metadata, JsonSerializer jsonSerializer) { - var queue = _target.GetRequestExecutionQueue(); - var lspServices = _target.GetLspServices(); + if (request is null && metadata.RequestType is not null) + { + throw new InvalidOperationException($"Handler {metadata.HandlerDescription} requires request parameters but received none"); + } + + if (request is not null && metadata.RequestType is null) + { + throw new InvalidOperationException($"Handler {metadata.HandlerDescription} does not accept parameters, but received some."); + } - var result = await queue.ExecuteAsync(NoValue.Instance, _method, lspServices, cancellationToken).ConfigureAwait(false); + object requestObject = NoValue.Instance; + if (request is not null) + { + requestObject = request.ToObject(metadata.RequestType, jsonSerializer) + ?? throw new InvalidOperationException($"Unable to deserialize {request} into {metadata.RequestType} for {metadata.HandlerDescription}"); + } - return result; + return requestObject; } } @@ -272,7 +307,7 @@ async Task Shutdown_NoLockAsync(string message) // Immediately yield so that this does not run under the lock. await Task.Yield(); - _logger.LogInformation(message); + Logger.LogInformation(message); // Allow implementations to do any additional cleanup on shutdown. var lifeCycleManager = GetLspServices().GetRequiredService(); @@ -330,7 +365,7 @@ async Task Exit_NoLockAsync() } finally { - _logger.LogInformation("Exiting server"); + Logger.LogInformation("Exiting server"); _serverExitedSource.TrySetResult(null); } } @@ -379,9 +414,9 @@ internal TestAccessor(AbstractLanguageServer server) return null; } - internal Task ExecuteRequestAsync(string methodName, TRequest request, CancellationToken cancellationToken) + internal Task ExecuteRequestAsync(string methodName, string languageName, TRequest request, CancellationToken cancellationToken) { - return _server._queue.Value.ExecuteAsync(request, methodName, _server._lspServices.Value, cancellationToken); + return _server._queue.Value.ExecuteAsync(request, methodName, languageName, _server._lspServices.Value, cancellationToken); } internal JsonRpc GetServerRpc() => _server._jsonRpc; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs index 41c4bf4001219..b849768f60027 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs @@ -9,11 +9,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractLspLogger : ILspLogger -#else internal abstract class AbstractLspLogger : ILspLogger -#endif { public abstract void LogDebug(string message, params object[] @params); public abstract void LogStartContext(string message, params object[] @params); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs index adcc85e1c484b..84849c484dc7a 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs @@ -21,11 +21,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// /// The type of the RequestContext to be used by the handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractRequestContextFactory -#else internal abstract class AbstractRequestContextFactory -#endif { /// /// Create a object from the given . diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs index 7e5ea5c8aeb5a..27762af48782d 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractRequestScope(string name) : IDisposable -#else internal abstract class AbstractRequestScope(string name) : IDisposable -#endif { public string Name { get; } = name; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs index 7df8edce2f545..c5ccdf85ab942 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs @@ -7,11 +7,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractTelemetryService -#else internal abstract class AbstractTelemetryService -#endif { public abstract AbstractRequestScope CreateRequestScope(string lspMethodName); } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs index ac0e0bd0ad03b..93d0b2f64e2b5 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs @@ -15,7 +15,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// -internal class HandlerProvider : AbstractHandlerProvider, IHandlerProvider +internal class HandlerProvider : AbstractHandlerProvider { private readonly ILspServices _lspServices; private ImmutableDictionary>? _requestHandlers; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs index aeb2a3d698f06..564898a683de1 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs @@ -11,11 +11,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.Handlers; [LanguageServerEndpoint("initialize", LanguageServerConstants.DefaultLanguageName)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class InitializeHandler -#else internal class InitializeHandler -#endif : IRequestHandler { private readonly IInitializeManager _capabilitiesManager; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs index 46f87a4c52df4..d4a14851e4a70 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs @@ -12,11 +12,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.Handlers; [LanguageServerEndpoint("initialized", LanguageServerConstants.DefaultLanguageName)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class InitializedHandler : INotificationHandler -#else internal class InitializedHandler : INotificationHandler -#endif { private bool HasBeenInitialized = false; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs deleted file mode 100644 index 837cd4b051de5..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable -#nullable enable - -using System; -using System.Collections.Immutable; - -namespace Microsoft.CommonLanguageServerProtocol.Framework; - -/// -/// Manages handler discovery and distribution. -/// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IHandlerProvider -#else -internal interface IHandlerProvider -#endif -{ - ImmutableArray GetRegisteredMethods(); - - IMethodHandler GetMethodHandler(string method, Type? requestType, Type? responseType); -} diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs index 13e9fc9293a3e..2a73d500c0919 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs @@ -6,12 +6,7 @@ #nullable enable namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IInitializeManager -#else internal interface IInitializeManager -#endif { /// /// Gets a response to be used for "initialize", completing the negoticaitons between client and server. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs index 73b17b89df6ad..b8f704c68d15f 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs @@ -13,11 +13,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An optional component to run additional logic when LSP shutdown and exit are called, /// for example logging messages, cleaning up custom resources, etc. /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ILifeCycleManager -#else internal interface ILifeCycleManager -#endif { /// /// Called when the server recieves the LSP exit notification. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs index 3bfe075fc3e0e..e7ac139a4ddc2 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ILspLogger -#else internal interface ILspLogger -#endif { void LogStartContext(string message, params object[] @params); void LogEndContext(string message, params object[] @params); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs index 14ed351fb7e7c..7b094ed23ea64 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs @@ -10,12 +10,7 @@ using System.Collections.Immutable; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ILspServices : IDisposable -#else internal interface ILspServices : IDisposable -#endif { T GetRequiredService() where T : notnull; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs index d8299e27250a1..6c637662c618d 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs @@ -10,11 +10,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// Top level type for LSP request handler. /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IMethodHandler -#else internal interface IMethodHandler -#endif { /// /// Whether or not the solution state on the server is modified as a part of handling this request. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs index 22c8d8f2c38a1..513a78dcc9c70 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs @@ -14,11 +14,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An interface for handlers of methods which do not return a response and receive no parameters. /// /// The type of the RequestContext to be used by this handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface INotificationHandler : IMethodHandler -#else internal interface INotificationHandler : IMethodHandler -#endif { Task HandleNotificationAsync(TRequestContext requestContext, CancellationToken cancellationToken); } @@ -28,11 +24,7 @@ internal interface INotificationHandler : IMethodHandler /// /// The type of the Request parameter to be received. /// The type of the RequestContext to be used by this handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface INotificationHandler : IMethodHandler -#else internal interface INotificationHandler : IMethodHandler -#endif { Task HandleNotificationAsync(TRequest request, TRequestContext requestContext, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs index 832bbf4fdeae2..23f1ea4cd188e 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs @@ -15,11 +15,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An item to be queued for execution. /// /// The type of the request context to be passed along to the handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IQueueItem -#else internal interface IQueueItem -#endif { /// /// Executes the work specified by this queue item. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs deleted file mode 100644 index ee07e97cfdc0a..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Threading; -using System.Threading.Tasks; - -// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable -#nullable enable - -namespace Microsoft.CommonLanguageServerProtocol.Framework; - -/// -/// -/// A factory for creating objects from 's. -/// -/// -/// RequestContext's are useful for passing document context, since by default -/// is run on the queue thread (and thus no mutating requests may be executing simultaneously, preventing possible race conditions). -/// It also allows somewhere to pass things like the or which are useful on a wide variety of requests. -/// -/// -/// The type of the RequestContext to be used by the handler. -[Obsolete($"Use {nameof(AbstractRequestContextFactory)} instead.", error: false)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestContextFactory -#else -internal interface IRequestContextFactory -#endif -{ - /// - /// Create a object from the given . - /// Note - throwing in the implementation of this method will cause the server to shutdown. - /// - /// The from which to create a request. - /// The request parameters. - /// - /// The for this request. - /// This method is called on the queue thread to allow context to be retrieved serially, without the possibility of race conditions from Mutating requests. - Task CreateRequestContextAsync(IQueueItem queueItem, TRequestParam requestParam, CancellationToken cancellationToken); -} diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs index bae2a3c7ff1e8..463e597c70cf3 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs @@ -15,17 +15,13 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// Queues requests to be executed in the proper order. /// /// The type of the RequestContext to be used by the handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestExecutionQueue : IAsyncDisposable -#else internal interface IRequestExecutionQueue : IAsyncDisposable -#endif { /// /// Queue a request. /// /// A task that completes when the handler execution is done. - Task ExecuteAsync(TRequest request, string methodName, ILspServices lspServices, CancellationToken cancellationToken); + Task ExecuteAsync(TRequest request, string methodName, string languageName, ILspServices lspServices, CancellationToken cancellationToken); /// /// Start the queue accepting requests once any event handlers have been attached. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs index 2bc711aabe3a2..7d58b68427183 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs @@ -9,12 +9,7 @@ using System.Threading.Tasks; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestHandler : IMethodHandler -#else internal interface IRequestHandler : IMethodHandler -#endif { /// /// Handles an LSP request in the context of the supplied document and/or solution. @@ -26,11 +21,7 @@ internal interface IRequestHandler : IMeth Task HandleRequestAsync(TRequest request, TRequestContext context, CancellationToken cancellationToken); } -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestHandler : IMethodHandler -#else internal interface IRequestHandler : IMethodHandler -#endif { /// /// Handles an LSP request in the context of the supplied document and/or solution. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs index eaf30967bc0b9..8c746ab34f188 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs @@ -7,11 +7,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ITextDocumentIdentifierHandler : ITextDocumentIdentifierHandler -#else internal interface ITextDocumentIdentifierHandler : ITextDocumentIdentifierHandler -#endif { /// /// Gets the identifier of the document from the request, if the request provides one. @@ -19,10 +15,6 @@ internal interface ITextDocumentIdentifierHandler /// Default language name for use with and . diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs index 1f8c71ce2d7ab..a7bb1e13ac058 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs @@ -14,11 +14,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An attribute which identifies the method which an implements. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = false)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class LanguageServerEndpointAttribute : Attribute -#else internal class LanguageServerEndpointAttribute : Attribute -#endif { /// /// Contains the method that this implements. @@ -45,6 +41,6 @@ public LanguageServerEndpointAttribute(string method) public LanguageServerEndpointAttribute(string method, string language, params string[] additionalLanguages) { Method = method; - Languages = new[] { language }.Concat(additionalLanguages).ToArray(); + Languages = [language, .. additionalLanguages]; } } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems index 3e94a73112030..4f299de12be5e 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems @@ -18,7 +18,6 @@ - @@ -26,7 +25,6 @@ - @@ -36,7 +34,6 @@ - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs index 5cf8e93cc2383..31dff9c3b2b81 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs @@ -86,30 +86,16 @@ public static (IQueueItem, Task) Create( return (queueItem, queueItem._completionSource.Task); } -#pragma warning disable CS0618 // Type or member is obsolete public async Task CreateRequestContextAsync(IMethodHandler handler, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); _requestTelemetryScope?.RecordExecutionStart(); - var requestContextFactory = (AbstractRequestContextFactory?)LspServices.TryGetService(typeof(AbstractRequestContextFactory)); - if (requestContextFactory is not null) - { - var context = await requestContextFactory.CreateRequestContextAsync(this, handler, _request, cancellationToken).ConfigureAwait(false); - return context; - } - - var obsoleteContextFactory = (IRequestContextFactory?)LspServices.TryGetService(typeof(IRequestContextFactory)); - if (obsoleteContextFactory is not null) - { - var context = await obsoleteContextFactory.CreateRequestContextAsync(this, _request, cancellationToken).ConfigureAwait(false); - return context; - } - - throw new InvalidOperationException($"No {nameof(AbstractRequestContextFactory)} or {nameof(IRequestContextFactory)} was registered with {nameof(ILspServices)}."); + var requestContextFactory = LspServices.GetRequiredService>(); + var context = await requestContextFactory.CreateRequestContextAsync(this, handler, _request, cancellationToken).ConfigureAwait(false); + return context; } -#pragma warning restore CS0618 // Type or member is obsolete /// /// Processes the queued request. Exceptions will be sent to the task completion source diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs index ad65a6f58fbd5..cd200a1ca0ec7 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.Threading; +using Newtonsoft.Json.Linq; namespace Microsoft.CommonLanguageServerProtocol.Framework; @@ -36,7 +37,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// Regardless of whether a request is mutating or not, or blocking or not, is an implementation detail of this class /// and any consumers observing the results of the task returned from -/// +/// /// will see the results of the handling of the request, whenever it occurred. /// /// @@ -49,11 +50,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// more messages, and a new queue will need to be created. /// /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class RequestExecutionQueue : IRequestExecutionQueue -#else internal class RequestExecutionQueue : IRequestExecutionQueue -#endif { protected readonly ILspLogger _logger; protected readonly AbstractHandlerProvider _handlerProvider; @@ -74,19 +71,6 @@ internal class RequestExecutionQueue : IRequestExecutionQueue _cancelSource.Token; - [Obsolete($"Use constructor with {nameof(AbstractHandlerProvider)} instead.", error: false)] - public RequestExecutionQueue(AbstractLanguageServer languageServer, ILspLogger logger, IHandlerProvider handlerProvider) - { - _languageServer = languageServer; - _logger = logger; - if (handlerProvider is AbstractHandlerProvider abstractHandlerProvider) - { - _handlerProvider = abstractHandlerProvider; - } - - _handlerProvider = new WrappedHandlerProvider(handlerProvider); - } - public RequestExecutionQueue(AbstractLanguageServer languageServer, ILspLogger logger, AbstractHandlerProvider handlerProvider) { _languageServer = languageServer; @@ -126,6 +110,7 @@ public void Start() public virtual Task ExecuteAsync( TRequest request, string methodName, + string languageName, ILspServices lspServices, CancellationToken requestCancellationToken) { @@ -137,6 +122,7 @@ public virtual Task ExecuteAsync( var combinedCancellationToken = combinedTokenSource.Token; var (item, resultTask) = CreateQueueItem( methodName, + languageName, request, lspServices, combinedCancellationToken); @@ -158,23 +144,19 @@ public virtual Task ExecuteAsync( internal (IQueueItem, Task) CreateQueueItem( string methodName, + string languageName, TRequest request, ILspServices lspServices, CancellationToken cancellationToken) { - var language = GetLanguageForRequest(methodName, request); - return QueueItem.Create(methodName, - language, + languageName, request, lspServices, _logger, cancellationToken); } - protected virtual string GetLanguageForRequest(string methodName, TRequest request) - => LanguageServerConstants.DefaultLanguageName; - private async Task ProcessQueueAsync() { ILspServices? lspServices = null; @@ -308,6 +290,16 @@ private async Task ProcessQueueAsync() } } + /// + /// Allows XAML to inspect the request before its dispatched. + /// Should not generally be used, this will be replaced by the OOP XAML server. + /// + [Obsolete("Only for use by legacy XAML LSP")] + protected internal virtual void BeforeRequest(TRequest request) + { + return; + } + /// /// Choose the method handler for the given request. By default this calls the . /// diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs index a4e41c02d182b..c0b0bd4254d43 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs @@ -8,9 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public record RequestHandlerMetadata(string MethodName, Type? RequestType, Type? ResponseType, string Language); -#else -internal record RequestHandlerMetadata(string MethodName, Type? RequestType, Type? ResponseType, string Language); -#endif +internal record RequestHandlerMetadata(string MethodName, Type? RequestType, Type? ResponseType, string Language) +{ + internal string HandlerDescription { get; } = $"{MethodName} ({Language})"; +} diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs index e689d852425f1..9ad2023834c93 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class RequestShutdownEventArgs : EventArgs -#else internal class RequestShutdownEventArgs : EventArgs -#endif { public string Message { get; } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs deleted file mode 100644 index 120b54dfb0c21..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable -#nullable enable - -using System; -using System.Collections.Immutable; - -namespace Microsoft.CommonLanguageServerProtocol.Framework; - -/// -/// Wraps an . -/// -internal sealed class WrappedHandlerProvider : AbstractHandlerProvider -{ - private readonly IHandlerProvider _handlerProvider; - - public WrappedHandlerProvider(IHandlerProvider handlerProvider) - { - _handlerProvider = handlerProvider; - } - - public override IMethodHandler GetMethodHandler(string method, Type? requestType, Type? responseType, string language) - => _handlerProvider.GetMethodHandler(method, requestType, responseType); - - public override ImmutableArray GetRegisteredMethods() - => _handlerProvider.GetRegisteredMethods(); -} diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/contentFiles/.editorconfig b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/contentFiles/.editorconfig index 3bd53642047ae..43cb3d22d6e6b 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/contentFiles/.editorconfig +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/contentFiles/.editorconfig @@ -7,4 +7,8 @@ root = true # We don't want any analyzer diagnostics to be reported for people consuming this as a source package. dotnet_analyzer_diagnostic.severity = none -generated_code = true \ No newline at end of file +generated_code = true + +# The above configurations don't apply to compiler warnings. Requiring all params to be documented +# is not something we require for this project, so suppressing it directly here. +dotnet_diagnostic.CS1573.severity = none \ No newline at end of file diff --git a/src/Features/LanguageServer/Protocol/CSharpVisualBasicLanguageServerFactory.cs b/src/Features/LanguageServer/Protocol/CSharpVisualBasicLanguageServerFactory.cs index 67ecd1ee51d0c..5e2276385b282 100644 --- a/src/Features/LanguageServer/Protocol/CSharpVisualBasicLanguageServerFactory.cs +++ b/src/Features/LanguageServer/Protocol/CSharpVisualBasicLanguageServerFactory.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CommonLanguageServerProtocol.Framework; +using Newtonsoft.Json; using StreamJsonRpc; namespace Microsoft.CodeAnalysis.LanguageServer @@ -29,6 +30,7 @@ public CSharpVisualBasicLanguageServerFactory( public AbstractLanguageServer Create( JsonRpc jsonRpc, + JsonSerializer jsonSerializer, ICapabilitiesProvider capabilitiesProvider, WellKnownLspServerKinds serverKind, AbstractLspLogger logger, @@ -37,6 +39,7 @@ public AbstractLanguageServer Create( var server = new RoslynLanguageServer( _lspServiceProvider, jsonRpc, + jsonSerializer, capabilitiesProvider, logger, hostServices, @@ -45,11 +48,5 @@ public AbstractLanguageServer Create( return server; } - - public AbstractLanguageServer Create(Stream input, Stream output, ICapabilitiesProvider capabilitiesProvider, AbstractLspLogger logger, HostServices hostServices) - { - var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(output, input)); - return Create(jsonRpc, capabilitiesProvider, WellKnownLspServerKinds.CSharpVisualBasicLspServer, logger, hostServices); - } } } diff --git a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs index 2b2b974ac5a7c..406ea9a4c2de0 100644 --- a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs +++ b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs @@ -98,7 +98,7 @@ public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) Range = true, Legend = new SemanticTokensLegend { - TokenTypes = SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes.ToArray(), + TokenTypes = [.. SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes], TokenModifiers = SemanticTokensSchema.TokenModifiers } }; @@ -120,16 +120,6 @@ public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) // Using VS server capabilities because we have our own custom client. capabilities.OnAutoInsertProvider = new VSInternalDocumentOnAutoInsertOptions { TriggerCharacters = ["'", "/", "\n"] }; - if (!supportsVsExtensions) - { - capabilities.DiagnosticOptions = new DiagnosticOptions - { - InterFileDependencies = true, - WorkDoneProgress = true, - WorkspaceDiagnostics = true, - }; - } - return capabilities; } diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index 890759ebb9ceb..d9b8e4078f89f 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -10,8 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; diff --git a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index 3a9f045e2e595..b46fbf4e76b96 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -95,6 +95,17 @@ internal static partial class ProtocolConversions { WellKnownTags.NuGet, ImmutableArray.Create(LSP.CompletionItemKind.Text) } }.ToImmutableDictionary(); + /// + /// Mapping from tags to LSP completion item tags. The value lists the potential LSP tags from + /// least-preferred to most preferred. More preferred kinds will be chosen if the client states they support + /// it. This mapping allows values including extensions to the kinds defined by VS (but not in the core LSP + /// spec). + /// + public static readonly ImmutableDictionary> RoslynTagToCompletionItemTags = new Dictionary>() + { + { WellKnownTags.Deprecated, ImmutableArray.Create(LSP.CompletionItemTag.Deprecated) }, + }.ToImmutableDictionary(); + // TO-DO: More LSP.CompletionTriggerKind mappings are required to properly map to Roslyn CompletionTriggerKinds. // https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1178726 public static async Task LSPToRoslynCompletionTriggerAsync( @@ -188,6 +199,22 @@ public static Uri CreateAbsoluteUri(string absolutePath) } } + internal static Uri CreateRelativePatternBaseUri(string path) + { + // According to VSCode LSP RelativePattern spec, + // found at https://github.com/microsoft/vscode/blob/9e1974682eb84eebb073d4ae775bad1738c281f6/src/vscode-dts/vscode.d.ts#L2226 + // the baseUri should not end in a trailing separator, nor should it + // have any relative segmeents (., ..) + if (path[^1] == System.IO.Path.DirectorySeparatorChar) + { + path = path[..^1]; + } + + Debug.Assert(!path.Split(System.IO.Path.DirectorySeparatorChar).Any(p => p == "." || p == "..")); + + return CreateAbsoluteUri(path); + } + // Implements workaround for https://github.com/dotnet/runtime/issues/89538: internal static string GetAbsoluteUriString(string absolutePath) { @@ -379,7 +406,7 @@ public static LSP.Range TextSpanToRange(TextSpan textSpan, SourceText text) else { var newText = await newDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - textChanges = newText.GetTextChanges(oldText).ToImmutableArray(); + textChanges = [.. newText.GetTextChanges(oldText)]; } // Map all the text changes' spans for this document. diff --git a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs index f30b9ac390f0d..fde0bc07d9891 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs @@ -15,7 +15,7 @@ private class ProjectCodeFixProvider : AbstractProjectExtensionProvider { protected override ImmutableArray GetLanguages(ExportCodeFixProviderAttribute exportAttribute) - => exportAttribute.Languages.ToImmutableArray(); + => [.. exportAttribute.Languages]; protected override bool TryGetExtensionsFromReference(AnalyzerReference reference, out ImmutableArray extensions) { diff --git a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs index 021e607a10c04..52f031d5db3a5 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs @@ -23,7 +23,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -118,7 +117,8 @@ public async Task GetMostSevereFixAsync( allDiagnostics = allDiagnostics.AddRange(copilotDiagnostics); var buildOnlyDiagnosticsService = document.Project.Solution.Services.GetRequiredService(); - allDiagnostics = allDiagnostics.AddRange(buildOnlyDiagnosticsService.GetBuildOnlyDiagnostics(document.Id)); + allDiagnostics = allDiagnostics.AddRange( + await buildOnlyDiagnosticsService.GetBuildOnlyDiagnosticsAsync(document.Id, cancellationToken).ConfigureAwait(false)); var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var spanToDiagnostics = ConvertToMap(text, allDiagnostics); @@ -206,7 +206,7 @@ public async IAsyncEnumerable StreamFixesAsync( diagnostics = diagnostics.AddRange(copilotDiagnostics); var buildOnlyDiagnosticsService = document.Project.Solution.Services.GetRequiredService(); - var buildOnlyDiagnostics = buildOnlyDiagnosticsService.GetBuildOnlyDiagnostics(document.Id); + var buildOnlyDiagnostics = await buildOnlyDiagnosticsService.GetBuildOnlyDiagnosticsAsync(document.Id, cancellationToken).ConfigureAwait(false); if (diagnostics.IsEmpty && buildOnlyDiagnostics.IsEmpty) yield break; @@ -255,20 +255,10 @@ private static async Task> GetCopilotDiagnosticsA CodeActionRequestPriority? priority, CancellationToken cancellationToken) { - if (!(priority is null or CodeActionRequestPriority.Low) - || document is not Document sourceDocument) - { - return []; - } + if (priority is null or CodeActionRequestPriority.Low) + return await document.GetCachedCopilotDiagnosticsAsync(range, cancellationToken).ConfigureAwait(false); - // Expand the fixable range for Copilot diagnostics to containing method. - // TODO: Share the below code with other places we compute containing method for Copilot analysis. - var root = await sourceDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var syntaxFacts = sourceDocument.GetRequiredLanguageService(); - var containingMethod = syntaxFacts.GetContainingMethodDeclaration(root, range.Start, useFullSpan: false); - range = containingMethod?.Span ?? range; - - return await document.GetCachedCopilotDiagnosticsAsync(range, cancellationToken).ConfigureAwait(false); + return []; } private static SortedDictionary> ConvertToMap( @@ -669,7 +659,7 @@ private static async Task> GetCodeFixesAsync( var task = fixer.RegisterCodeFixesAsync(context) ?? Task.CompletedTask; await task.ConfigureAwait(false); - return fixes.ToImmutable(); + return fixes.ToImmutableAndClear(); static ImmutableArray FilterApplicableDiagnostics( ImmutableArray applicableDiagnostics, @@ -941,14 +931,12 @@ private static ImmutableDictionary GetConfigurationFixProviders(ImmutableArray> languageKindAndFixers) { - using var builderDisposer = ArrayBuilder.GetInstance(out var builder); var orderedLanguageKindAndFixers = ExtensionOrderer.Order(languageKindAndFixers); + var builder = new FixedSizeArrayBuilder(orderedLanguageKindAndFixers.Count); foreach (var languageKindAndFixersValue in orderedLanguageKindAndFixers) - { builder.Add(languageKindAndFixersValue.Value); - } - return builder.ToImmutable(); + return builder.MoveToImmutable(); } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index 507922c291017..be32623daf749 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -26,12 +26,6 @@ namespace Microsoft.CodeAnalysis.Diagnostics [Shared] internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService { - private const string DiagnosticsUpdatedEventName = "DiagnosticsUpdated"; - - // use eventMap and taskQueue to serialize events - private readonly EventMap _eventMap = new(); - private readonly TaskQueue _eventQueue; - public DiagnosticAnalyzerInfoCache AnalyzerInfoCache { get; private set; } public IAsynchronousOperationListener Listener { get; } @@ -39,11 +33,11 @@ internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService private readonly ConditionalWeakTable _map = new(); private readonly ConditionalWeakTable.CreateValueCallback _createIncrementalAnalyzer; + private readonly IDiagnosticsRefresher _diagnosticsRefresher; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public DiagnosticAnalyzerService( - IDiagnosticUpdateSourceRegistrationService registrationService, IAsynchronousOperationListenerProvider listenerProvider, DiagnosticAnalyzerInfoCache.SharedGlobalCache globalCache, IGlobalOptionService globalOptions, @@ -52,18 +46,14 @@ public DiagnosticAnalyzerService( AnalyzerInfoCache = globalCache.AnalyzerInfoCache; Listener = listenerProvider.GetListener(FeatureAttribute.DiagnosticService); GlobalOptions = globalOptions; - + _diagnosticsRefresher = diagnosticsRefresher; _createIncrementalAnalyzer = CreateIncrementalAnalyzerCallback; - _eventQueue = new TaskQueue(Listener, TaskScheduler.Default); - - registrationService.Register(this); - globalOptions.AddOptionChangedHandler(this, (_, e) => { if (IsGlobalOptionAffectingDiagnostics(e.Option)) { - diagnosticsRefresher.RequestWorkspaceRefresh(); + RequestDiagnosticRefresh(); } }); } @@ -75,9 +65,8 @@ public static bool IsGlobalOptionAffectingDiagnostics(IOption2 option) option == SolutionCrawlerOptionsStorage.SolutionBackgroundAnalysisScopeOption || option == SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption; - public void Reanalyze(Workspace workspace, IEnumerable? projectIds, IEnumerable? documentIds, bool highPriority) - { - } + public void RequestDiagnosticRefresh() + => _diagnosticsRefresher?.RequestWorkspaceRefresh(); public Task<(ImmutableArray diagnostics, bool upToDate)> TryGetDiagnosticsForSpanAsync( TextDocument document, @@ -132,12 +121,6 @@ public Task> GetCachedDiagnosticsAsync(Workspace return analyzer.GetCachedDiagnosticsAsync(workspace.CurrentSolution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } - public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - { - var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); - } - public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); @@ -145,10 +128,10 @@ public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken ca } public Task> GetDiagnosticsForIdsAsync( - Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs deleted file mode 100644 index 62a6da55c689b..0000000000000 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.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. - -#nullable disable - -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics -{ - internal partial class DiagnosticAnalyzerService - { - /// - /// Synchronize build errors with live error. - /// - public ValueTask SynchronizeWithBuildAsync( - Workspace workspace, - ImmutableDictionary> diagnostics, - TaskQueue postBuildAndErrorListRefreshTaskQueue, - bool onBuildCompleted, - CancellationToken cancellationToken) - { - return _map.TryGetValue(workspace, out var analyzer) - ? analyzer.SynchronizeWithBuildAsync(diagnostics, postBuildAndErrorListRefreshTaskQueue, onBuildCompleted, cancellationToken) - : default; - } - } -} diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs index b52fa731ff259..99d9760bc40ca 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs @@ -5,8 +5,6 @@ using System; using Microsoft.CodeAnalysis.Diagnostics.EngineV2; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Internal.Log; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics; @@ -23,9 +21,9 @@ private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzerCallback(Workspac // subscribe to active context changed event for new workspace workspace.DocumentActiveContextChanged += OnDocumentActiveContextChanged; - return new DiagnosticIncrementalAnalyzer(this, CorrelationIdFactory.GetNextId(), workspace, AnalyzerInfoCache); + return new DiagnosticIncrementalAnalyzer(this, workspace, AnalyzerInfoCache); } private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContextChangedEventArgs e) - => Reanalyze(e.Solution.Workspace, projectIds: null, documentIds: SpecializedCollections.SingletonEnumerable(e.NewActiveContextDocumentId), highPriority: true); + => RequestDiagnosticRefresh(); } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs deleted file mode 100644 index 2d4a1d39db757..0000000000000 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs +++ /dev/null @@ -1,103 +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.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics -{ - internal partial class DiagnosticAnalyzerService : IDiagnosticUpdateSource - { - public event EventHandler> DiagnosticsUpdated - { - add - { - _eventMap.AddEventHandler(DiagnosticsUpdatedEventName, value); - } - - remove - { - _eventMap.RemoveEventHandler(DiagnosticsUpdatedEventName, value); - } - } - - public event EventHandler DiagnosticsCleared - { - add - { - // don't do anything. this update source doesn't use cleared event - } - - remove - { - // don't do anything. this update source doesn't use cleared event - } - } - - internal void RaiseDiagnosticsUpdated(ImmutableArray args) - { - if (args.IsEmpty) - return; - - // all diagnostics events are serialized. - var ev = _eventMap.GetEventHandlers>>(DiagnosticsUpdatedEventName); - if (ev.HasHandlers) - { - _eventQueue.ScheduleTask(nameof(RaiseDiagnosticsUpdated), () => ev.RaiseEvent(static (handler, arg) => handler(arg.self, arg.args), (self: this, args)), CancellationToken.None); - } - } - - internal void RaiseBulkDiagnosticsUpdated(Action>> eventAction) - { - // all diagnostics events are serialized. - var ev = _eventMap.GetEventHandlers>>(DiagnosticsUpdatedEventName); - if (ev.HasHandlers) - { - // we do this bulk update to reduce number of tasks (with captured data) enqueued. - // we saw some "out of memory" due to us having long list of pending tasks in memory. - // this is to reduce for such case to happen. - void raiseEvents(ImmutableArray args) - { - if (args.IsEmpty) - return; - - ev.RaiseEvent( - static (handler, arg) => handler(arg.self, arg.args), - (self: this, args)); - } - - _eventQueue.ScheduleTask(nameof(RaiseDiagnosticsUpdated), () => eventAction(raiseEvents), CancellationToken.None); - } - } - - internal void RaiseBulkDiagnosticsUpdated(Func>, Task> eventActionAsync) - { - // all diagnostics events are serialized. - var ev = _eventMap.GetEventHandlers>>(DiagnosticsUpdatedEventName); - if (ev.HasHandlers) - { - // we do this bulk update to reduce number of tasks (with captured data) enqueued. - // we saw some "out of memory" due to us having long list of pending tasks in memory. - // this is to reduce for such case to happen. - void raiseEvents(ImmutableArray args) - { - ev.RaiseEvent( - static (handler, arg) => - { - if (!arg.args.IsEmpty) - handler(arg.self, arg.args); - }, - (self: this, args)); - } - - _eventQueue.ScheduleTask(nameof(RaiseDiagnosticsUpdated), () => eventActionAsync(raiseEvents), CancellationToken.None); - } - } - } -} diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService.cs deleted file mode 100644 index 38e15e438c93e..0000000000000 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService.cs +++ /dev/null @@ -1,269 +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.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Collections; -using Microsoft.CodeAnalysis.Common; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Shared.Collections; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics -{ - [Export(typeof(IDiagnosticService)), Shared] - internal partial class DiagnosticService : IDiagnosticService, IDisposable - { - private const string DiagnosticsUpdatedEventName = "DiagnosticsUpdated"; - - private readonly EventMap _eventMap = new(); - private readonly CancellationTokenSource _eventQueueCancellation = new(); - private readonly AsyncBatchingWorkQueue<(BatchOperation operation, IDiagnosticUpdateSource source, ImmutableArray argsCollection)> _eventQueue; - - private readonly object _gate = new(); - private readonly Dictionary>> _map = []; - - private ImmutableHashSet _updateSources; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DiagnosticService( - IAsynchronousOperationListenerProvider listenerProvider, - [ImportMany] IEnumerable> eventListeners) - { - // we use registry service rather than doing MEF import since MEF import method can have race issue where - // update source gets created before aggregator - diagnostic service - is created and we will lose events - // fired before the aggregator is created. - _updateSources = []; - - // queue to serialize events. - _eventQueue = new AsyncBatchingWorkQueue<(BatchOperation operation, IDiagnosticUpdateSource source, ImmutableArray argsCollection)>( - delay: TimeSpan.Zero, - ProcessEventsBatchAsync, - listenerProvider.GetListener(FeatureAttribute.DiagnosticService), - _eventQueueCancellation.Token); - } - - private enum BatchOperation - { - DiagnosticsUpdated, - DiagnosticsCleared, - } - - public event EventHandler> DiagnosticsUpdated - { - add - { - _eventMap.AddEventHandler(DiagnosticsUpdatedEventName, value); - } - - remove - { - _eventMap.RemoveEventHandler(DiagnosticsUpdatedEventName, value); - } - } - - void IDisposable.Dispose() - { - _eventQueueCancellation.Cancel(); - } - - private ValueTask ProcessEventsBatchAsync(ImmutableSegmentedList<(BatchOperation operation, IDiagnosticUpdateSource source, ImmutableArray argsCollection)> batch, CancellationToken cancellationToken) - { - var ev = _eventMap.GetEventHandlers>>(DiagnosticsUpdatedEventName); - - foreach (var (operation, source, argsCollection) in batch) - { - if (operation == BatchOperation.DiagnosticsUpdated) - { - var updatedArgsCollection = UpdateDataMap(source, argsCollection); - if (updatedArgsCollection.IsEmpty) - { - // there is no change, nothing to raise events for. - continue; - } - - ev.RaiseEvent(static (handler, arg) => handler(arg.source, arg.updatedArgsCollection), (source, updatedArgsCollection)); - } - else if (operation == BatchOperation.DiagnosticsCleared) - { - using var argsBuilder = TemporaryArray.Empty; - - if (!ClearDiagnosticsReportedBySource(source, ref argsBuilder.AsRef())) - { - // there is no change, nothing to raise events for. - continue; - } - - // don't create event listener if it haven't created yet. if there is a diagnostic to remove - // listener should have already created since all events are done in the serialized queue - ev.RaiseEvent(static (handler, arg) => handler(arg.source, arg.args), (source, args: argsBuilder.ToImmutableAndClear())); - } - else - { - throw ExceptionUtilities.UnexpectedValue(operation); - } - } - - return ValueTaskFactory.CompletedTask; - } - - private void RaiseDiagnosticsUpdated(IDiagnosticUpdateSource source, ImmutableArray argsCollection) - { - _eventQueue.AddWork((BatchOperation.DiagnosticsUpdated, source, argsCollection)); - } - - private void RaiseDiagnosticsCleared(IDiagnosticUpdateSource source) - { - _eventQueue.AddWork((BatchOperation.DiagnosticsCleared, source, ImmutableArray.Empty)); - } - - private ImmutableArray UpdateDataMap(IDiagnosticUpdateSource source, ImmutableArray argsCollection) - { - // we expect source who uses this ability to have small number of diagnostics. - lock (_gate) - { - var result = argsCollection.WhereAsArray(args => - { - Debug.Assert(_updateSources.Contains(source)); - - var diagnostics = args.Diagnostics; - - // check cheap early bail out - if (diagnostics.Length == 0 && !_map.ContainsKey(source)) - { - // no new diagnostic, and we don't have update source for it. - return false; - } - - // 2 different workspaces (ex, PreviewWorkspaces) can return same Args.Id, we need to - // distinguish them. so we separate diagnostics per workspace map. - var workspaceMap = _map.GetOrAdd(source, _ => []); - - if (diagnostics.Length == 0 && !workspaceMap.ContainsKey(args.Workspace)) - { - // no new diagnostic, and we don't have workspace for it. - return false; - } - - var diagnosticDataMap = workspaceMap.GetOrAdd(args.Workspace, _ => []); - - diagnosticDataMap.Remove(args.Id); - if (diagnosticDataMap.Count == 0 && diagnostics.Length == 0) - { - workspaceMap.Remove(args.Workspace); - - if (workspaceMap.Count == 0) - { - _map.Remove(source); - } - - return true; - } - - if (diagnostics.Length > 0) - { - var data = new Data(args, diagnostics); - diagnosticDataMap.Add(args.Id, data); - } - - return true; - }); - - return result; - } - } - - private bool ClearDiagnosticsReportedBySource(IDiagnosticUpdateSource source, ref TemporaryArray removed) - { - // we expect source who uses this ability to have small number of diagnostics. - lock (_gate) - { - Debug.Assert(_updateSources.Contains(source)); - - // 2 different workspaces (ex, PreviewWorkspaces) can return same Args.Id, we need to - // distinguish them. so we separate diagnostics per workspace map. - if (!_map.TryGetValue(source, out var workspaceMap)) - { - return false; - } - - foreach (var (workspace, map) in workspaceMap) - { - foreach (var (id, data) in map) - { - removed.Add(DiagnosticsUpdatedArgs.DiagnosticsRemoved(id, data.Workspace, solution: null, data.ProjectId, data.DocumentId)); - } - } - - // all diagnostics from the source is cleared - _map.Remove(source); - return true; - } - } - - private void OnDiagnosticsUpdated(object? sender, ImmutableArray e) - { - AssertIfNull(e.SelectManyAsArray(e => e.Diagnostics)); - - // all events are serialized by async event handler - RaiseDiagnosticsUpdated((IDiagnosticUpdateSource)sender!, e); - } - - private void OnCleared(object? sender, EventArgs e) - { - // all events are serialized by async event handler - RaiseDiagnosticsCleared((IDiagnosticUpdateSource)sender!); - } - - [Conditional("DEBUG")] - private static void AssertIfNull(ImmutableArray diagnostics) - { - for (var i = 0; i < diagnostics.Length; i++) - { - AssertIfNull(diagnostics[i]); - } - } - - [Conditional("DEBUG")] - private static void AssertIfNull(T obj) - where T : class - { - if (obj == null) - { - Debug.Assert(false, "who returns invalid data?"); - } - } - - private readonly struct Data - { - public readonly Workspace Workspace; - public readonly ProjectId? ProjectId; - public readonly DocumentId? DocumentId; - public readonly object Id; - public readonly ImmutableArray Diagnostics; - - public Data(UpdatedEventArgs args) - : this(args, []) - { - } - - public Data(UpdatedEventArgs args, ImmutableArray diagnostics) - { - Workspace = args.Workspace; - ProjectId = args.ProjectId; - DocumentId = args.DocumentId; - Id = args.Id; - Diagnostics = diagnostics; - } - } - } -} diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService_UpdateSourceRegistrationService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService_UpdateSourceRegistrationService.cs deleted file mode 100644 index f586248984e64..0000000000000 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService_UpdateSourceRegistrationService.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Composition; - -namespace Microsoft.CodeAnalysis.Diagnostics -{ - [Export(typeof(IDiagnosticUpdateSourceRegistrationService))] - internal partial class DiagnosticService : IDiagnosticUpdateSourceRegistrationService - { - public void Register(IDiagnosticUpdateSource source) - { - lock (_gate) - { - if (_updateSources.Contains(source)) - { - return; - } - - _updateSources = _updateSources.Add(source); - - source.DiagnosticsUpdated += OnDiagnosticsUpdated; - source.DiagnosticsCleared += OnCleared; - } - } - } -} diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs index f785abc52551a..321e5cb7ee1f2 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs @@ -82,14 +82,12 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti if (analyzer == FileContentLoadAnalyzer.Instance) { return loadDiagnostic != null - ? SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, textDocument)) - : SpecializedCollections.EmptyEnumerable(); + ? [DiagnosticData.Create(loadDiagnostic, textDocument)] + : []; } if (loadDiagnostic != null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (analyzer == GeneratorDiagnosticsPlaceholderAnalyzer.Instance) { @@ -101,16 +99,14 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti } else { - return SpecializedCollections.EmptyEnumerable(); + return []; } } if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) { if (document == null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var documentDiagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( documentAnalyzer, document, kind, _compilationWithAnalyzers?.Compilation, cancellationToken).ConfigureAwait(false); @@ -127,7 +123,7 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", _compilationWithAnalyzers, textDocument, analyzer, kind); } - return SpecializedCollections.EmptyEnumerable(); + return []; } // if project is not loaded successfully then, we disable semantic errors for compiler analyzers @@ -139,16 +135,12 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti Logger.Log(FunctionId.Diagnostics_SemanticDiagnostic, (a, d, e) => $"{a}, ({d.Id}, {d.Project.Id}), Enabled:{e}", analyzer, textDocument, isEnabled); if (!isEnabled) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; } + // We currently support document analysis only for source documents and additional documents. if (document == null && textDocument is not AdditionalDocument) - { - // We currently support document analysis only for source documents and additional documents. - return SpecializedCollections.EmptyEnumerable(); - } + return []; var diagnostics = kind switch { @@ -403,14 +395,14 @@ private static async Task> RemapDiagnosticLocatio } // Round tripping the diagnostics should ensure they get correctly remapped. - using var _ = ArrayBuilder.GetInstance(diagnostics.Length, out var builder); + var builder = new FixedSizeArrayBuilder(diagnostics.Length); foreach (var diagnosticData in diagnostics) { var diagnostic = await diagnosticData.ToDiagnosticAsync(textDocument.Project, cancellationToken).ConfigureAwait(false); builder.Add(DiagnosticData.Create(diagnostic, textDocument)); } - return builder.ToImmutableAndClear(); + return builder.MoveToImmutable(); } } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index a2fd206083a6a..68cdf1724c25b 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -3,7 +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.Diagnostics; using System.Linq; @@ -12,7 +11,6 @@ using Microsoft.CodeAnalysis.Diagnostics.Telemetry; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; @@ -22,164 +20,11 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { internal partial class DiagnosticIncrementalAnalyzer { - /// - /// Return all cached local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer). - /// Otherwise, return null. - /// For the latter case, indicates if the analyzer is suppressed - /// for the given document/project. If suppressed, the caller does not need to compute the diagnostics for the given - /// analyzer. Otherwise, diagnostics need to be computed. - /// - private (ActiveFileState, DocumentAnalysisData?) TryGetCachedDocumentAnalysisData( - TextDocument document, StateSet stateSet, - AnalysisKind kind, VersionStamp version, - BackgroundAnalysisScope analysisScope, - CompilerDiagnosticsScope compilerDiagnosticsScope, - bool isActiveDocument, bool isVisibleDocument, - bool isOpenDocument, bool isGeneratedRazorDocument, - CancellationToken cancellationToken, - out bool isAnalyzerSuppressed) - { - Debug.Assert(isActiveDocument || isOpenDocument || isGeneratedRazorDocument); - - isAnalyzerSuppressed = false; - - try - { - var state = stateSet.GetOrCreateActiveFileState(document.Id); - var existingData = state.GetAnalysisData(kind); - - if (existingData.Version == version) - { - return (state, existingData); - } - - // Check whether analyzer is suppressed for project or document. - // If so, we set the flag indicating that the client can skip analysis for this document. - // Regardless of whether or not the analyzer is suppressed for project or document, - // we return null to indicate that no diagnostics are cached for this document for the given version. - isAnalyzerSuppressed = !DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(stateSet.Analyzer, document.Project, GlobalOptions) || - !IsAnalyzerEnabledForDocument(stateSet.Analyzer, existingData, analysisScope, compilerDiagnosticsScope, - isActiveDocument, isVisibleDocument, isOpenDocument, isGeneratedRazorDocument); - return (state, null); - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) - { - throw ExceptionUtilities.Unreachable(); - } - - static bool IsAnalyzerEnabledForDocument( - DiagnosticAnalyzer analyzer, - DocumentAnalysisData previousData, - BackgroundAnalysisScope analysisScope, - CompilerDiagnosticsScope compilerDiagnosticsScope, - bool isActiveDocument, - bool isVisibleDocument, - bool isOpenDocument, - bool isGeneratedRazorDocument) - { - Debug.Assert(!isActiveDocument || isOpenDocument || isGeneratedRazorDocument); - - if (isGeneratedRazorDocument) - { - // This is a generated Razor document, and they always want all analyzer diagnostics. - return true; - } - - if (analyzer.IsCompilerAnalyzer()) - { - return compilerDiagnosticsScope switch - { - // Compiler diagnostics are disabled for all documents. - CompilerDiagnosticsScope.None => false, - - // Compiler diagnostics are enabled for visible documents and open documents which had errors/warnings in prior snapshot. - CompilerDiagnosticsScope.VisibleFilesAndOpenFilesWithPreviouslyReportedDiagnostics => IsVisibleDocumentOrOpenDocumentWithPriorReportedVisibleDiagnostics(isVisibleDocument, isOpenDocument, previousData), - - // Compiler diagnostics are enabled for all open documents. - CompilerDiagnosticsScope.OpenFiles => isOpenDocument, - - // Compiler diagnostics are enabled for all documents. - CompilerDiagnosticsScope.FullSolution => true, - - _ => throw ExceptionUtilities.UnexpectedValue(analysisScope) - }; - } - else - { - return analysisScope switch - { - // Analyzers are disabled for all documents. - BackgroundAnalysisScope.None => false, - - // Analyzers are enabled for visible documents and open documents which had errors/warnings in prior snapshot. - BackgroundAnalysisScope.VisibleFilesAndOpenFilesWithPreviouslyReportedDiagnostics => IsVisibleDocumentOrOpenDocumentWithPriorReportedVisibleDiagnostics(isVisibleDocument, isOpenDocument, previousData), - - // Analyzers are enabled for all open documents. - BackgroundAnalysisScope.OpenFiles => isOpenDocument, - - // Analyzers are enabled for all documents. - BackgroundAnalysisScope.FullSolution => true, - - _ => throw ExceptionUtilities.UnexpectedValue(analysisScope) - }; - } - } - - static bool IsVisibleDocumentOrOpenDocumentWithPriorReportedVisibleDiagnostics( - bool isVisibleDocument, - bool isOpenDocument, - DocumentAnalysisData previousData) - { - if (isVisibleDocument) - return true; - - return isOpenDocument - && previousData.Items.Any(static d => d.Severity is DiagnosticSeverity.Error or DiagnosticSeverity.Warning or DiagnosticSeverity.Info); - } - } - - /// - /// Computes all local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer). - /// - private static async Task ComputeDocumentAnalysisDataAsync( - DocumentAnalysisExecutor executor, DiagnosticAnalyzer analyzer, ActiveFileState state, bool logTelemetry, CancellationToken cancellationToken) - { - var kind = executor.AnalysisScope.Kind; - var document = executor.AnalysisScope.TextDocument; - - // get log title and functionId - GetLogFunctionIdAndTitle(kind, out var functionId, out var title); - - var logLevel = logTelemetry ? LogLevel.Information : LogLevel.Trace; - using (Logger.LogBlock(functionId, GetDocumentLogMessage, title, document, analyzer, cancellationToken, logLevel: logLevel)) - { - try - { - var diagnostics = await executor.ComputeDiagnosticsAsync(analyzer, cancellationToken).ConfigureAwait(false); - - // this is no-op in product. only run in test environment - Logger.Log(functionId, (t, d, a, ds) => $"{GetDocumentLogMessage(t, d, a)}, {string.Join(Environment.NewLine, ds)}", - title, document, analyzer, diagnostics); - - var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); - var existingData = state.GetAnalysisData(kind); - var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - - // we only care about local diagnostics - return new DocumentAnalysisData(version, text.Lines.Count, existingData.Items, diagnostics.ToImmutableArrayOrEmpty()); - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) - { - throw ExceptionUtilities.Unreachable(); - } - } - } - /// /// Return all diagnostics that belong to given project for the given StateSets (analyzers) either from cache or by calculating them /// private async Task GetProjectAnalysisDataAsync( - CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, bool forceAnalyzerRun, CancellationToken cancellationToken) + CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, project, stateSets, cancellationToken)) { @@ -197,64 +42,7 @@ private async Task GetProjectAnalysisDataAsync( return existingData; } - // PERF: Check whether we want to analyze this project or not. - var fullAnalysisEnabled = GlobalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullAnalysisEnabled, out var analyzersFullAnalysisEnabled); - if (forceAnalyzerRun) - { - // We are forcing full solution analysis for all diagnostics. - fullAnalysisEnabled = true; - compilerFullAnalysisEnabled = true; - analyzersFullAnalysisEnabled = true; - } - - if (!fullAnalysisEnabled) - { - Logger.Log(FunctionId.Diagnostics_ProjectDiagnostic, p => $"FSA off ({p.FilePath ?? p.Name})", project); - - // If we are producing document diagnostics for some other document in this project, we still want to show - // certain project-level diagnostics that would cause file-level diagnostics to be broken. We will only do this though if - // some file that's open is depending on this project though -- that way we're going to only be analyzing projects - // that have already had compilations produced for. - var shouldProduceOutput = false; - - var projectDependencyGraph = project.Solution.GetProjectDependencyGraph(); - - foreach (var openDocumentId in project.Solution.Workspace.GetOpenDocumentIds()) - { - if (openDocumentId.ProjectId == project.Id || projectDependencyGraph.DoesProjectTransitivelyDependOnProject(openDocumentId.ProjectId, project.Id)) - { - shouldProduceOutput = true; - break; - } - } - - var results = ImmutableDictionary.Empty; - - if (shouldProduceOutput) - { - (results, _) = await UpdateWithDocumentLoadAndGeneratorFailuresAsync( - results, - project, - version, - cancellationToken).ConfigureAwait(false); - } - - return new ProjectAnalysisData(project.Id, VersionStamp.Default, existingData.Result, results); - } - - // Reduce the state sets to analyze based on individual full solution analysis values - // for compiler diagnostics and analyzers. - if (!compilerFullAnalysisEnabled) - { - Debug.Assert(analyzersFullAnalysisEnabled); - stateSets = stateSets.WhereAsArray(s => !s.Analyzer.IsCompilerAnalyzer()); - } - else if (!analyzersFullAnalysisEnabled) - { - stateSets = stateSets.WhereAsArray(s => s.Analyzer.IsCompilerAnalyzer() || s.Analyzer.IsWorkspaceDiagnosticAnalyzer()); - } - - var result = await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideOptions, stateSets, forceAnalyzerRun, existingData.Result, cancellationToken).ConfigureAwait(false); + var result = await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideOptions, stateSets, existingData.Result, cancellationToken).ConfigureAwait(false); // If project is not loaded successfully, get rid of any semantic errors from compiler analyzer. // Note: In the past when project was not loaded successfully we did not run any analyzers on the project. @@ -324,7 +112,7 @@ private static async Task private async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzers? compilationWithAnalyzers, Project project, ImmutableArray ideAnalyzers, bool forcedAnalysis, CancellationToken cancellationToken) + CompilationWithAnalyzers? compilationWithAnalyzers, Project project, ImmutableArray ideAnalyzers, CancellationToken cancellationToken) { try { @@ -334,8 +122,8 @@ private async Task 0) { // calculate regular diagnostic analyzers diagnostics - var resultMap = await _diagnosticAnalyzerRunner.AnalyzeProjectAsync(project, compilationWithAnalyzers, - forcedAnalysis, logPerformanceInfo: false, getTelemetryInfo: true, cancellationToken).ConfigureAwait(false); + var resultMap = await _diagnosticAnalyzerRunner.AnalyzeProjectAsync( + project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: true, cancellationToken).ConfigureAwait(false); result = resultMap.AnalysisResult; @@ -353,7 +141,7 @@ private async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, bool forcedAnalysis, + CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, ImmutableDictionary existing, CancellationToken cancellationToken) { try @@ -380,12 +168,12 @@ await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync( compilationWithAnalyzers.AnalysisOptions.ReportSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - var result = await ComputeDiagnosticsAsync(compilationWithReducedAnalyzers, project, ideAnalyzers, forcedAnalysis, cancellationToken).ConfigureAwait(false); + var result = await ComputeDiagnosticsAsync(compilationWithReducedAnalyzers, project, ideAnalyzers, cancellationToken).ConfigureAwait(false); return MergeExistingDiagnostics(version, existing, result); } // we couldn't reduce the set. - return await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideAnalyzers, forcedAnalysis, cancellationToken).ConfigureAwait(false); + return await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideAnalyzers, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -566,22 +354,5 @@ private void UpdateAnalyzerTelemetryData(ImmutableDictionary> AnalyzeProjectAsync( Project project, CompilationWithAnalyzers compilationWithAnalyzers, - bool forceExecuteAllAnalyzers, bool logPerformanceInfo, bool getTelemetryInfo, CancellationToken cancellationToken) => AnalyzeAsync(documentAnalysisScope: null, project, compilationWithAnalyzers, - isExplicit: false, forceExecuteAllAnalyzers, logPerformanceInfo, getTelemetryInfo, cancellationToken); + isExplicit: false, forceExecuteAllAnalyzers: true, logPerformanceInfo, getTelemetryInfo, cancellationToken); private async Task> AnalyzeAsync( DocumentAnalysisScope? documentAnalysisScope, @@ -214,7 +213,7 @@ private static async Task LoadInitialProjectAnalysisDataAsync return builder.ToResult(); } - private ValueTask AddToInMemoryStorageAsync( + private void AddToInMemoryStorage( VersionStamp serializerVersion, Project project, TextDocument? document, object key, string stateKey, ImmutableArray diagnostics) { Contract.ThrowIfFalse(document == null || document.Project == project); // if serialization fail, hold it in the memory InMemoryStorage.Cache(_owner.Analyzer, (key, stateKey), new CacheEntry(serializerVersion, diagnostics)); - return default; } private async ValueTask TryGetDiagnosticsFromInMemoryStorageAsync(VersionStamp serializerVersion, TextDocument document, Builder builder, CancellationToken cancellationToken) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs index 756bda925fde5..e71c62e4e71d2 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs @@ -71,7 +71,7 @@ public HostAnalyzerStateSets(ImmutableDictionary a // order statesets // order will be in this order // BuiltIn Compiler Analyzer (C#/VB) < Regular DiagnosticAnalyzers < Document/ProjectDiagnosticAnalyzers - OrderedStateSets = StateSetMap.Values.OrderBy(PriorityComparison).ToImmutableArray(); + OrderedStateSets = [.. StateSetMap.Values.OrderBy(PriorityComparison)]; } public HostAnalyzerStateSets WithExcludedAnalyzers(ImmutableHashSet excludedAnalyzers) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs index 7d36dfaca4e24..9687fa9cc85d9 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs @@ -177,7 +177,7 @@ private static ImmutableArray DiffStateSets( } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index d3b1751751e4d..fe01283879f67 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -127,70 +127,6 @@ public IEnumerable GetOrCreateStateSets(Project project) return null; } - /// - /// Return s that are added as the given 's AnalyzerReferences. - /// This will never create new but will return ones already created. - /// - public ImmutableArray CreateBuildOnlyProjectStateSet(Project project) - { - var projectStateSets = project.SupportsCompilation - ? GetOrUpdateProjectStateSets(project) - : ProjectAnalyzerStateSets.Default; - var hostStateSets = GetOrCreateHostStateSets(project, projectStateSets); - - if (!project.SupportsCompilation) - { - // languages which don't use our compilation model but diagnostic framework, - // all their analyzer should be host analyzers. return all host analyzers - // for the language - return hostStateSets.OrderedStateSets; - } - - var hostStateSetMap = hostStateSets.StateSetMap; - - // create project analyzer reference identity map - var projectAnalyzerReferenceIds = project.AnalyzerReferences.Select(r => r.Id).ToSet(); - - // create build only stateSet array - var stateSets = ImmutableArray.CreateBuilder(); - - // include compiler analyzer in build only state, if available - StateSet? compilerStateSet = null; - var hostAnalyzers = project.Solution.SolutionState.Analyzers; - var compilerAnalyzer = hostAnalyzers.GetCompilerDiagnosticAnalyzer(project.Language); - if (compilerAnalyzer != null && hostStateSetMap.TryGetValue(compilerAnalyzer, out compilerStateSet)) - { - stateSets.Add(compilerStateSet); - } - - // now add all project analyzers - stateSets.AddRange(projectStateSets.StateSetMap.Values); - - // now add analyzers that exist in both host and project - var hostAnalyzersById = hostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(project.Language); - foreach (var (identity, analyzers) in hostAnalyzersById) - { - if (!projectAnalyzerReferenceIds.Contains(identity)) - { - // it is from host analyzer package rather than project analyzer reference - // which build doesn't have - continue; - } - - // if same analyzer exists both in host (vsix) and in analyzer reference, - // we include it in build only analyzer. - foreach (var analyzer in analyzers) - { - if (hostStateSetMap.TryGetValue(analyzer, out var stateSet) && stateSet != compilerStateSet) - { - stateSets.Add(stateSet); - } - } - } - - return stateSets.ToImmutable(); - } - public bool OnProjectRemoved(IEnumerable stateSets, ProjectId projectId) { var removed = false; diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs index 851898e113e0c..9a2f24933bc04 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs @@ -41,21 +41,17 @@ public IEnumerable GetProjectsWithDiagnostics() { // quick bail out if (_activeFileStates.IsEmpty && _projectStates.IsEmpty) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (_activeFileStates.Count == 1 && _projectStates.IsEmpty) { // see whether we actually have diagnostics var (documentId, state) = _activeFileStates.First(); if (state.IsEmpty) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; // we do have diagnostics - return SpecializedCollections.SingletonEnumerable(documentId.ProjectId); + return [documentId.ProjectId]; } return new HashSet( diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 4ee7e585fd01f..1ddadcfbd2bf1 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -5,14 +5,11 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; @@ -27,26 +24,17 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 /// internal partial class DiagnosticIncrementalAnalyzer { - private readonly int _correlationId; private readonly DiagnosticAnalyzerTelemetry _telemetry = new(); private readonly StateManager _stateManager; private readonly InProcOrRemoteHostAnalyzerRunner _diagnosticAnalyzerRunner; - private readonly IDocumentTrackingService _documentTrackingService; private readonly IncrementalMemberEditAnalyzer _incrementalMemberEditAnalyzer = new(); -#if NETSTANDARD - private ConditionalWeakTable _projectCompilationsWithAnalyzers = new(); -#else - private readonly ConditionalWeakTable _projectCompilationsWithAnalyzers = []; -#endif - internal DiagnosticAnalyzerService AnalyzerService { get; } internal Workspace Workspace { get; } [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] public DiagnosticIncrementalAnalyzer( DiagnosticAnalyzerService analyzerService, - int correlationId, Workspace workspace, DiagnosticAnalyzerInfoCache analyzerInfoCache) { @@ -55,10 +43,6 @@ public DiagnosticIncrementalAnalyzer( AnalyzerService = analyzerService; Workspace = workspace; - _documentTrackingService = workspace.Services.GetRequiredService(); - - _correlationId = correlationId; - _stateManager = new StateManager(workspace, analyzerInfoCache); _stateManager.ProjectAnalyzerReferenceChanged += OnProjectAnalyzerReferenceChanged; @@ -78,108 +62,11 @@ private void OnProjectAnalyzerReferenceChanged(object? sender, ProjectAnalyzerRe return; } - // events will be automatically serialized. - var project = e.Project; - var stateSets = e.Removed; - // make sure we drop cache related to the analyzers - foreach (var stateSet in stateSets) - { + foreach (var stateSet in e.Removed) stateSet.OnRemoved(); - } - - ClearAllDiagnostics(stateSets, project.Id); } - private void ClearAllDiagnostics(ImmutableArray stateSets, ProjectId projectId) - { - AnalyzerService.RaiseBulkDiagnosticsUpdated(raiseEvents => - { - using var _ = PooledHashSet.GetInstance(out var documentSet); - using var argsBuilder = TemporaryArray.Empty; - - foreach (var stateSet in stateSets) - { - Debug.Assert(documentSet.Count == 0); - - stateSet.CollectDocumentsWithDiagnostics(projectId, documentSet); - - // PERF: don't fire events for ones that we dont have any diagnostics on - if (documentSet.Count > 0) - { - AddProjectDiagnosticsRemovedArgs(ref argsBuilder.AsRef(), stateSet, projectId, documentSet, handleActiveFile: true); - documentSet.Clear(); - } - } - - raiseEvents(argsBuilder.ToImmutableAndClear()); - }); - } - - private void AddDiagnosticsCreatedArgs( - ref TemporaryArray builder, - Project project, DiagnosticAnalyzer analyzer, ImmutableArray items) - { - Contract.ThrowIfFalse(project.Solution.Workspace == Workspace); - - builder.Add(DiagnosticsUpdatedArgs.DiagnosticsCreated( - CreateId(analyzer, project.Id, AnalysisKind.NonLocal), - project.Solution.Workspace, - project.Solution, - project.Id, - documentId: null, - diagnostics: items)); - } - - private void AddDiagnosticsRemovedArgs( - ref TemporaryArray builder, - ProjectId projectId, Solution? solution, DiagnosticAnalyzer analyzer) - { - Contract.ThrowIfFalse(solution == null || solution.Workspace == Workspace); - - builder.Add(DiagnosticsUpdatedArgs.DiagnosticsRemoved( - CreateId(analyzer, projectId, AnalysisKind.NonLocal), - Workspace, - solution, - projectId, - documentId: null)); - } - - private void AddDiagnosticsCreatedArgs( - ref TemporaryArray builder, - TextDocument document, DiagnosticAnalyzer analyzer, AnalysisKind kind, ImmutableArray items) - { - Contract.ThrowIfFalse(document.Project.Solution.Workspace == Workspace); - - builder.Add(DiagnosticsUpdatedArgs.DiagnosticsCreated( - CreateId(analyzer, document.Id, kind), - document.Project.Solution.Workspace, - document.Project.Solution, - document.Project.Id, - document.Id, - items)); - } - - private void AddDiagnosticsRemovedArgs( - ref TemporaryArray builder, - DocumentId documentId, Solution? solution, DiagnosticAnalyzer analyzer, AnalysisKind kind) - { - Contract.ThrowIfFalse(solution == null || solution.Workspace == Workspace); - - builder.Add(DiagnosticsUpdatedArgs.DiagnosticsRemoved( - CreateId(analyzer, documentId, kind), - Workspace, - solution, - documentId.ProjectId, - documentId)); - } - - private static object CreateId(DiagnosticAnalyzer analyzer, DocumentId documentId, AnalysisKind kind) - => new LiveDiagnosticUpdateArgsId(analyzer, documentId, kind); - - private static object CreateId(DiagnosticAnalyzer analyzer, ProjectId projectId, AnalysisKind kind) - => new LiveDiagnosticUpdateArgsId(analyzer, projectId, kind); - public static Task GetDiagnosticVersionAsync(Project project, CancellationToken cancellationToken) => project.GetDependentVersionAsync(cancellationToken); @@ -196,22 +83,10 @@ private static DiagnosticAnalysisResult GetResultOrEmpty(ImmutableDictionary GetAnalyzersTestOnly(Project project) => _stateManager.GetOrCreateStateSets(project).Select(s => s.Analyzer); - private static string GetDocumentLogMessage(string title, TextDocument document, DiagnosticAnalyzer analyzer) - => $"{title}: ({document.Id}, {document.Project.Id}), ({analyzer})"; - private static string GetProjectLogMessage(Project project, ImmutableArray stateSets) => $"project: ({project.Id}), ({string.Join(Environment.NewLine, stateSets.Select(s => s.Analyzer.ToString()))})"; - private static string GetResetLogMessage(TextDocument document) - => $"document close/reset: ({document.FilePath ?? document.Name})"; - private static string GetOpenLogMessage(TextDocument document) => $"document open: ({document.FilePath ?? document.Name})"; - - private static string GetRemoveLogMessage(DocumentId id) - => $"document remove: {id.ToString()}"; - - private static string GetRemoveLogMessage(ProjectId id) - => $"project remove: {id.ToString()}"; } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs deleted file mode 100644 index eec6bc4234f96..0000000000000 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs +++ /dev/null @@ -1,211 +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.Globalization; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.Workspaces.Diagnostics; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 -{ - internal partial class DiagnosticIncrementalAnalyzer - { - public async ValueTask SynchronizeWithBuildAsync( - ImmutableDictionary> buildDiagnostics, - TaskQueue postBuildAndErrorListRefreshTaskQueue, - bool onBuildCompleted, - CancellationToken cancellationToken) - { - using (Logger.LogBlock(FunctionId.DiagnosticIncrementalAnalyzer_SynchronizeWithBuildAsync, LogSynchronizeWithBuild, buildDiagnostics, cancellationToken)) - { - DebugVerifyBuildDiagnostics(buildDiagnostics); - - var solution = Workspace.CurrentSolution; - - foreach (var (projectId, diagnostics) in buildDiagnostics) - { - cancellationToken.ThrowIfCancellationRequested(); - - var project = solution.GetProject(projectId); - if (project == null) - { - continue; - } - - var stateSets = _stateManager.CreateBuildOnlyProjectStateSet(project); - var newResult = CreateAnalysisResults(project, stateSets, diagnostics); - - // PERF: Save the diagnostics into in-memory cache on the main thread. - // Saving them into persistent storage is expensive, so we invoke that operation on a separate task queue - // to ensure faster error list refresh. - foreach (var stateSet in stateSets) - { - cancellationToken.ThrowIfCancellationRequested(); - - var state = stateSet.GetOrCreateProjectState(project.Id); - var result = GetResultOrEmpty(newResult, stateSet.Analyzer, project.Id, VersionStamp.Default); - - await state.SaveToInMemoryStorageAsync(project, result).ConfigureAwait(false); - } - - // Raise diagnostic updated events after the new diagnostics have been stored into the in-memory cache. - if (diagnostics.IsEmpty) - { - ClearAllDiagnostics(stateSets, projectId); - } - else - { - RaiseProjectDiagnosticsIfNeeded(project, stateSets, newResult); - } - } - - // Refresh live diagnostics after solution build completes. - if (onBuildCompleted) - { - // Enqueue re-analysis of active document with high-priority right away. - if (_documentTrackingService.GetActiveDocument(solution) is { } activeDocument) - { - AnalyzerService.Reanalyze(Workspace, projectIds: null, documentIds: ImmutableArray.Create(activeDocument.Id), highPriority: true); - } - - // Enqueue remaining re-analysis with normal priority on a separate task queue - // that will execute at the end of all the post build and error list refresh tasks. - _ = postBuildAndErrorListRefreshTaskQueue.ScheduleTask(nameof(SynchronizeWithBuildAsync), () => - { - // Enqueue re-analysis of open documents. - AnalyzerService.Reanalyze(Workspace, projectIds: null, documentIds: Workspace.GetOpenDocumentIds(), highPriority: false); - - // Enqueue re-analysis of projects, if required. - foreach (var projectsByLanguage in solution.Projects.GroupBy(p => p.Language)) - { - if (GlobalOptions.IsFullSolutionAnalysisEnabled(projectsByLanguage.Key)) - { - AnalyzerService.Reanalyze(Workspace, projectsByLanguage.Select(p => p.Id), documentIds: null, highPriority: false); - } - } - }, cancellationToken); - } - } - } - - [Conditional("DEBUG")] - private static void DebugVerifyBuildDiagnostics(ImmutableDictionary> buildDiagnostics) - { - foreach (var diagnostic in buildDiagnostics.Values.SelectMany(v => v)) - { - Debug.Assert(diagnostic.IsBuildDiagnostic()); - } - } - - private ImmutableDictionary CreateAnalysisResults( - Project project, ImmutableArray stateSets, ImmutableArray diagnostics) - { - using var poolObject = SharedPools.Default>().GetPooledObject(); - - var lookup = diagnostics.ToLookup(d => d.Id); - - var builder = ImmutableDictionary.CreateBuilder(); - using var _ = PooledHashSet.GetInstance(out var existingDocumentsInStateSet); - foreach (var stateSet in stateSets) - { - var descriptors = DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(stateSet.Analyzer); - var liveDiagnostics = ConvertToLiveDiagnostics(lookup, descriptors, poolObject.Object); - - // Ensure that all documents with diagnostics in the previous state set are added to the result. - existingDocumentsInStateSet.Clear(); - stateSet.CollectDocumentsWithDiagnostics(project.Id, existingDocumentsInStateSet); - - builder.Add(stateSet.Analyzer, DiagnosticAnalysisResult.CreateFromBuild(project, liveDiagnostics, existingDocumentsInStateSet)); - } - - return builder.ToImmutable(); - } - - private static ImmutableArray ConvertToLiveDiagnostics( - ILookup lookup, ImmutableArray descriptors, HashSet seen) - { - if (lookup == null) - { - return []; - } - - ImmutableArray.Builder? builder = null; - foreach (var descriptor in descriptors) - { - // make sure we don't report same id to multiple different analyzers - if (!seen.Add(descriptor.Id)) - { - // TODO: once we have information where diagnostic came from, we probably don't need this. - continue; - } - - var items = lookup[descriptor.Id]; - if (items == null) - { - continue; - } - - builder ??= ImmutableArray.CreateBuilder(); - builder.AddRange(items.Select(d => CreateLiveDiagnostic(descriptor, d))); - } - - return builder == null ? [] : builder.ToImmutable(); - } - - private static DiagnosticData CreateLiveDiagnostic(DiagnosticDescriptor descriptor, DiagnosticData diagnostic) - { - return new DiagnosticData( - descriptor.Id, - descriptor.Category, - diagnostic.Message, - diagnostic.Severity, - descriptor.DefaultSeverity, - descriptor.IsEnabledByDefault, - diagnostic.WarningLevel, - descriptor.ImmutableCustomTags(), - diagnostic.Properties, - diagnostic.ProjectId, - diagnostic.DataLocation, - diagnostic.AdditionalLocations, - diagnostic.Language, - descriptor.Title.ToString(CultureInfo.CurrentUICulture), - descriptor.Description.ToString(CultureInfo.CurrentUICulture), - descriptor.HelpLinkUri, - isSuppressed: diagnostic.IsSuppressed); - } - - private static string LogSynchronizeWithBuild(ImmutableDictionary> map) - { - using var pooledObject = SharedPools.Default().GetPooledObject(); - var sb = pooledObject.Object; - - if (map.Count > 0) - { - foreach (var (projectId, diagnostics) in map) - { - sb.AppendLine($"{projectId}, Count: {diagnostics.Length}"); - - foreach (var diagnostic in diagnostics) - { - sb.AppendLine($" {diagnostic}"); - } - } - } - - return sb.ToString(); - } - } -} diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 7bf0a3fbb7bf0..f24820a50f801 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -18,14 +18,11 @@ internal partial class DiagnosticIncrementalAnalyzer public Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeCachedDiagnosticGetter(this, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, getDocuments: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); private abstract class DiagnosticGetter { @@ -38,6 +35,8 @@ private abstract class DiagnosticGetter protected readonly bool IncludeLocalDocumentDiagnostics; protected readonly bool IncludeNonLocalDocumentDiagnostics; + private readonly Func> _getDocuments; + private ImmutableArray.Builder? _lazyDataBuilder; public DiagnosticGetter( @@ -45,6 +44,7 @@ public DiagnosticGetter( Solution solution, ProjectId? projectId, DocumentId? documentId, + Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) @@ -53,6 +53,7 @@ public DiagnosticGetter( Solution = solution; DocumentId = documentId; + _getDocuments = getDocuments ?? (static (project, documentId) => documentId != null ? [documentId] : project.DocumentIds); ProjectId = projectId ?? documentId?.ProjectId; IncludeSuppressedDiagnostics = includeSuppressedDiagnostics; @@ -79,7 +80,7 @@ public async Task> GetDiagnosticsAsync(Cancellati return GetDiagnosticData(); } - var documentIds = (DocumentId != null) ? SpecializedCollections.SingletonEnumerable(DocumentId) : project.DocumentIds; + var documentIds = _getDocuments(project, DocumentId); // return diagnostics specific to one project or document var includeProjectNonLocalResult = DocumentId == null; @@ -132,7 +133,7 @@ private bool ShouldIncludeSuppressedDiagnostic(DiagnosticData diagnostic) private sealed class IdeCachedDiagnosticGetter : DiagnosticGetter { public IdeCachedDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) - : base(owner, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) + : base(owner, solution, projectId, documentId, getDocuments: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) { } @@ -232,8 +233,9 @@ private sealed class IdeLatestDiagnosticGetter : DiagnosticGetter public IdeLatestDiagnosticGetter( DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, - bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) - : base(owner, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) + Func>? getDocuments, + bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) + : base(owner, solution, projectId, documentId, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) { _diagnosticIds = diagnosticIds; _shouldIncludeAnalyzer = shouldIncludeAnalyzer; @@ -246,7 +248,7 @@ public async Task> GetProjectDiagnosticsAsync(Can var project = Solution.GetProject(ProjectId); if (project != null) { - await AppendDiagnosticsAsync(project, SpecializedCollections.EmptyEnumerable(), includeProjectNonLocalResult: true, cancellationToken).ConfigureAwait(false); + await AppendDiagnosticsAsync(project, documentIds: [], includeProjectNonLocalResult: true, cancellationToken).ConfigureAwait(false); } return GetDiagnosticData(); @@ -269,7 +271,7 @@ protected override async Task AppendDiagnosticsAsync(Project project, IEnumerabl // unlike the suppressed (disabled) analyzer, we will include hidden diagnostic only analyzers here. var compilation = await CreateCompilationWithAnalyzersAsync(project, ideOptions, stateSets, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - var result = await Owner.GetProjectAnalysisDataAsync(compilation, project, ideOptions, stateSets, forceAnalyzerRun: true, cancellationToken).ConfigureAwait(false); + var result = await Owner.GetProjectAnalysisDataAsync(compilation, project, ideOptions, stateSets, cancellationToken).ConfigureAwait(false); foreach (var stateSet in stateSets) { diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index ef77efe45ac21..735e983e73719 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -52,7 +52,7 @@ public async Task> GetDiagnosticsForSpanAsync( document, range, list, shouldIncludeDiagnostic, includeSuppressedDiagnostics, includeCompilerDiagnostics, priorityProvider, blockForData, addOperationScope, diagnosticKinds, isExplicit, cancellationToken).ConfigureAwait(false); Debug.Assert(result); - return list.ToImmutable(); + return list.ToImmutableAndClear(); } /// diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 187f528b30431..e7054c9de2c94 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -3,29 +3,22 @@ // 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.ErrorReporting; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { internal partial class DiagnosticIncrementalAnalyzer { - public Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) - => AnalyzeProjectAsync(project, forceAnalyzerRun: true, cancellationToken); - - private async Task AnalyzeProjectAsync(Project project, bool forceAnalyzerRun, CancellationToken cancellationToken) + public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { try { @@ -38,35 +31,29 @@ private async Task AnalyzeProjectAsync(Project project, bool forceAnalyzerRun, C // this is perf optimization. we cache these result since we know the result. (no diagnostics) var activeAnalyzers = stateSets .Select(s => s.Analyzer) - .Where(a => (forceAnalyzerRun || DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(a, project, GlobalOptions)) && !a.IsOpenFileOnly(ideOptions.CleanupOptions?.SimplifierOptions)); + .Where(a => !a.IsOpenFileOnly(ideOptions.CleanupOptions?.SimplifierOptions)); CompilationWithAnalyzers? compilationWithAnalyzers = null; - if (forceAnalyzerRun || GlobalOptions.IsFullSolutionAnalysisEnabled(project.Language)) - { - compilationWithAnalyzers = await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync(project, ideOptions, activeAnalyzers, includeSuppressedDiagnostics: true, cancellationToken).ConfigureAwait(false); - } + compilationWithAnalyzers = await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync(project, ideOptions, activeAnalyzers, includeSuppressedDiagnostics: true, cancellationToken).ConfigureAwait(false); - var result = await GetProjectAnalysisDataAsync(compilationWithAnalyzers, project, ideOptions, stateSets, forceAnalyzerRun, cancellationToken).ConfigureAwait(false); + var result = await GetProjectAnalysisDataAsync(compilationWithAnalyzers, project, ideOptions, stateSets, cancellationToken).ConfigureAwait(false); + + using var _ = ArrayBuilder.GetInstance(out var diagnostics); // no cancellation after this point. - using var _ = ArrayBuilder.GetInstance(out var analyzedStateSetsBuilder); foreach (var stateSet in stateSets) { var state = stateSet.GetOrCreateProjectState(project.Id); if (result.TryGetResult(stateSet.Analyzer, out var analyzerResult)) { + diagnostics.AddRange(analyzerResult.GetAllDiagnostics()); await state.SaveToInMemoryStorageAsync(project, analyzerResult).ConfigureAwait(false); - analyzedStateSetsBuilder.Add(stateSet); } } - if (analyzedStateSetsBuilder.Count > 0) - { - var oldResult = result.OldResult ?? ImmutableDictionary.Empty; - RaiseProjectDiagnosticsIfNeeded(project, analyzedStateSetsBuilder.ToImmutable(), oldResult, result.Result); - } + return diagnostics.ToImmutableAndClear(); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -163,178 +150,6 @@ private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, Pro return descriptors.Any(static (d, arg) => d.GetEffectiveSeverity(arg.CompilationOptions, arg.analyzerConfigOptions?.AnalyzerOptions, arg.analyzerConfigOptions?.TreeOptions) != ReportDiagnostic.Hidden, (project.CompilationOptions, analyzerConfigOptions)); } - private void RaiseProjectDiagnosticsIfNeeded( - Project project, - IEnumerable stateSets, - ImmutableDictionary result) - { - RaiseProjectDiagnosticsIfNeeded(project, stateSets, ImmutableDictionary.Empty, result); - } - - private void RaiseProjectDiagnosticsIfNeeded( - Project project, - IEnumerable stateSets, - ImmutableDictionary oldResult, - ImmutableDictionary newResult) - { - if (oldResult.Count == 0 && newResult.Count == 0) - { - // there is nothing to update - return; - } - - AnalyzerService.RaiseBulkDiagnosticsUpdated(async raiseEvents => - { - using var argsBuilder = TemporaryArray.Empty; - foreach (var stateSet in stateSets) - { - var analyzer = stateSet.Analyzer; - - var oldAnalysisResult = GetResultOrEmpty(oldResult, analyzer, project.Id, VersionStamp.Default); - var newAnalysisResult = GetResultOrEmpty(newResult, analyzer, project.Id, VersionStamp.Default); - - // Perf - 4 different cases. - // upper 3 cases can be removed and it will still work. but this is hot path so if we can bail out - // without any allocations, that's better. - if (oldAnalysisResult.IsEmpty && newAnalysisResult.IsEmpty) - { - // nothing to do - continue; - } - - if (!oldAnalysisResult.IsEmpty && newAnalysisResult.IsEmpty) - { - RoslynDebug.Assert(oldAnalysisResult.DocumentIds != null); - - // remove old diagnostics - AddProjectDiagnosticsRemovedArgs(ref argsBuilder.AsRef(), stateSet, oldAnalysisResult.ProjectId, oldAnalysisResult.DocumentIds, handleActiveFile: false); - continue; - } - - if (oldAnalysisResult.IsEmpty && !newAnalysisResult.IsEmpty) - { - // add new diagnostics - argsBuilder.AddRange(await CreateProjectDiagnosticsCreatedArgsAsync(project, stateSet, oldAnalysisResult, newAnalysisResult, CancellationToken.None).ConfigureAwait(false)); - continue; - } - - // both old and new has items in them. update existing items - RoslynDebug.Assert(oldAnalysisResult.DocumentIds != null); - RoslynDebug.Assert(newAnalysisResult.DocumentIds != null); - - // first remove ones no longer needed. - var documentsToRemove = oldAnalysisResult.DocumentIds.Except(newAnalysisResult.DocumentIds); - AddProjectDiagnosticsRemovedArgs(ref argsBuilder.AsRef(), stateSet, oldAnalysisResult.ProjectId, documentsToRemove, handleActiveFile: false); - - // next update or create new ones - argsBuilder.AddRange(await CreateProjectDiagnosticsCreatedArgsAsync(project, stateSet, oldAnalysisResult, newAnalysisResult, CancellationToken.None).ConfigureAwait(false)); - } - - raiseEvents(argsBuilder.ToImmutableAndClear()); - }); - } - - private void AddDocumentDiagnosticsArgsIfNeeded( - ref TemporaryArray builder, - TextDocument document, DiagnosticAnalyzer analyzer, AnalysisKind kind, - DiagnosticAnalysisResult oldResult, DiagnosticAnalysisResult newResult) - { - // if our old result is from build and we don't have actual data, don't try micro-optimize and always refresh diagnostics. - // most of time, we don't actually load or hold the old data in memory from persistent storage due to perf reasons. - // - // we need this special behavior for errors from build since unlike live errors, we don't know whether errors - // from build is for syntax, semantic or others. due to that, we blindly mark them as semantic errors (most common type of errors from build) - // - // that can sometime cause issues. for example, if the error turns out to be syntax error (live) then we at the end fail to de-dup. - // but since this optimization saves us a lot of refresh between live errors analysis we want to disable this only in this condition. - var forceUpdate = oldResult.FromBuild && oldResult.IsAggregatedForm; - - var oldItems = oldResult.GetDocumentDiagnostics(document.Id, kind); - var newItems = newResult.GetDocumentDiagnostics(document.Id, kind); - - AddDocumentDiagnosticsArgsIfNeeded(ref builder, document, analyzer, kind, oldItems, newItems, forceUpdate); - } - - private void AddDocumentDiagnosticsArgsIfNeeded( - ref TemporaryArray builder, - TextDocument document, DiagnosticAnalyzer analyzer, AnalysisKind kind, - ImmutableArray oldItems, ImmutableArray newItems, - bool forceUpdate) - { - if (!forceUpdate && oldItems.IsEmpty && newItems.IsEmpty) - { - // there is nothing to update - return; - } - - AddDiagnosticsCreatedArgs(ref builder, document, analyzer, kind, newItems); - } - - private async Task> CreateProjectDiagnosticsCreatedArgsAsync(Project project, StateSet stateSet, DiagnosticAnalysisResult oldAnalysisResult, DiagnosticAnalysisResult newAnalysisResult, CancellationToken cancellationToken) - { - RoslynDebug.Assert(newAnalysisResult.DocumentIds != null); - - using var argsBuilder = TemporaryArray.Empty; - foreach (var documentId in newAnalysisResult.DocumentIds) - { - var document = project.GetTextDocument(documentId); - - // If we couldn't find a normal document, and all features are enabled for source generated documents, - // attempt to locate a matching source generated document in the project. - if (document is null - && project.Solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true) - { - document = await project.GetSourceGeneratedDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); - } - - if (document == null) - { - // it can happen with build synchronization since, in build case, - // we don't have actual snapshot (we have no idea what sources out of proc build has picked up) - // so we might be out of sync. - // example of such cases will be changing anything about solution while building is going on. - // it can be user explicit actions such as unloading project, deleting a file, but also it can be - // something project system or roslyn workspace does such as populating workspace right after - // solution is loaded. - continue; - } - - AddDocumentDiagnosticsArgsIfNeeded(ref argsBuilder.AsRef(), document, stateSet.Analyzer, AnalysisKind.NonLocal, oldAnalysisResult, newAnalysisResult); - - // we don't raise events for active file. it will be taken cared by active file analysis - if (stateSet.IsActiveFile(documentId)) - { - continue; - } - - AddDocumentDiagnosticsArgsIfNeeded(ref argsBuilder.AsRef(), document, stateSet.Analyzer, AnalysisKind.Syntax, oldAnalysisResult, newAnalysisResult); - AddDocumentDiagnosticsArgsIfNeeded(ref argsBuilder.AsRef(), document, stateSet.Analyzer, AnalysisKind.Semantic, oldAnalysisResult, newAnalysisResult); - } - - AddDiagnosticsCreatedArgs(ref argsBuilder.AsRef(), project, stateSet.Analyzer, newAnalysisResult.GetOtherDiagnostics()); - - return argsBuilder.ToImmutableAndClear(); - } - - private void AddProjectDiagnosticsRemovedArgs(ref TemporaryArray builder, StateSet stateSet, ProjectId projectId, IEnumerable documentIds, bool handleActiveFile) - { - foreach (var documentId in documentIds) - { - AddDiagnosticsRemovedArgs(ref builder, documentId, solution: null, stateSet.Analyzer, AnalysisKind.NonLocal); - - // we don't raise events for active file. it will be taken care of by active file analysis - if (!handleActiveFile && stateSet.IsActiveFile(documentId)) - { - continue; - } - - AddDiagnosticsRemovedArgs(ref builder, documentId, solution: null, stateSet.Analyzer, AnalysisKind.Syntax); - AddDiagnosticsRemovedArgs(ref builder, documentId, solution: null, stateSet.Analyzer, AnalysisKind.Semantic); - } - - AddDiagnosticsRemovedArgs(ref builder, projectId, solution: null, stateSet.Analyzer); - } - public TestAccessor GetTestAccessor() => new(this); diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/IDiagnosticService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/IDiagnosticService.cs deleted file mode 100644 index 43521b1561ee9..0000000000000 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/IDiagnosticService.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.Immutable; - -namespace Microsoft.CodeAnalysis.Diagnostics -{ - /// - /// Aggregates events from various diagnostic sources. - /// - internal interface IDiagnosticService - { - /// - /// Event to get notified as new diagnostics are discovered by IDiagnosticUpdateSource - /// - /// Notifications for this event are serialized to preserve order. - /// However, individual event notifications may occur on any thread. - /// - event EventHandler> DiagnosticsUpdated; - } -} diff --git a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs new file mode 100644 index 0000000000000..deb7877c1b234 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.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.Collections.Immutable; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.EditAndContinue; + +internal static partial class EditAndContinueDiagnosticSource +{ + private sealed class OpenDocumentSource(Document document) : AbstractDocumentDiagnosticSource(document) + { + public override bool IsLiveSource() + => true; + + public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + var designTimeDocument = Document; + var designTimeSolution = designTimeDocument.Project.Solution; + var services = designTimeSolution.Services; + + // avoid creating and synchronizing compile-time solution if Hot Reload/EnC session is not active + if (services.GetRequiredService().SessionTracker is not { IsSessionActive: true } sessionStateTracker) + { + return []; + } + + var applyDiagnostics = sessionStateTracker.ApplyChangesDiagnostics.WhereAsArray(static (data, id) => data.DocumentId == id, designTimeDocument.Id); + + var compileTimeSolution = services.GetRequiredService().GetCompileTimeSolution(designTimeSolution); + + var compileTimeDocument = await CompileTimeSolutionProvider.TryGetCompileTimeDocumentAsync(designTimeDocument, compileTimeSolution, cancellationToken).ConfigureAwait(false); + if (compileTimeDocument == null) + { + return applyDiagnostics; + } + + // EnC services should never be called on a design-time solution. + + var proxy = new RemoteEditAndContinueServiceProxy(services); + var spanLocator = services.GetService(); + + var activeStatementSpanProvider = spanLocator != null + ? new ActiveStatementSpanProvider((documentId, filePath, cancellationToken) => spanLocator.GetSpansAsync(compileTimeSolution, documentId, filePath, cancellationToken)) + : static (_, _, _) => ValueTaskFactory.FromResult(ImmutableArray.Empty); + + var rudeEditDiagnostics = await proxy.GetDocumentDiagnosticsAsync(compileTimeDocument, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); + + // TODO: remove + // We pretend the diagnostic is in the original document, but use the mapped line span. + // Razor will ignore the column (which will be off because #line directives can't currently map columns) and only use the line number. + rudeEditDiagnostics = rudeEditDiagnostics.SelectAsArray(data => (designTimeDocument != compileTimeDocument) ? RemapLocation(designTimeDocument, data) : data); + + return applyDiagnostics.AddRange(rudeEditDiagnostics); + } + + private static DiagnosticData RemapLocation(Document designTimeDocument, DiagnosticData data) + { + Debug.Assert(data.DataLocation != null); + Debug.Assert(designTimeDocument.FilePath != null); + + // If the location in the generated document is in a scope of user-visible #line mapping use the mapped span, + // otherwise (if it's hidden) display the diagnostic at the start of the file. + var span = data.DataLocation.UnmappedFileSpan != data.DataLocation.MappedFileSpan ? data.DataLocation.MappedFileSpan.Span : default; + var location = new DiagnosticDataLocation(new FileLinePositionSpan(designTimeDocument.FilePath, span)); + + return data.WithLocations(location, additionalLocations: []); + } + } + + public static IDiagnosticSource CreateOpenDocumentSource(Document document) + => new OpenDocumentSource(document); +} diff --git a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs new file mode 100644 index 0000000000000..b84fee77d2030 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.EditAndContinue; + +internal static partial class EditAndContinueDiagnosticSource +{ + private sealed class ProjectSource(Project project, ImmutableArray diagnostics) : AbstractProjectDiagnosticSource(project) + { + public override bool IsLiveSource() + => true; + + public override Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + => Task.FromResult(diagnostics); + } + + private sealed class ClosedDocumentSource(TextDocument document, ImmutableArray diagnostics) : AbstractWorkspaceDocumentDiagnosticSource(document) + { + public override bool IsLiveSource() + => true; + + public override Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + => Task.FromResult(diagnostics); + } + + public static async ValueTask> CreateWorkspaceDiagnosticSourcesAsync(Solution solution, Func isDocumentOpen, CancellationToken cancellationToken) + { + if (solution.Services.GetRequiredService().SessionTracker is not { IsSessionActive: true } sessionStateTracker) + { + return []; + } + + using var _ = ArrayBuilder.GetInstance(out var sources); + + var applyDiagnostics = sessionStateTracker.ApplyChangesDiagnostics; + + var dataByDocument = from data in applyDiagnostics + where data.DocumentId != null + group data by data.DocumentId into documentData + select documentData; + + // diagnostics associated with closed documents: + foreach (var (documentId, diagnostics) in dataByDocument) + { + var document = await solution.GetDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + if (document != null && !isDocumentOpen(document)) + { + sources.Add(new ClosedDocumentSource(document, diagnostics.ToImmutableArray())); + } + } + + // diagnostics not associated with a document: + sources.AddRange( + from data in applyDiagnostics + where data.DocumentId == null && data.ProjectId != null + group data by data.ProjectId into projectData + let project = solution.GetProject(projectData.Key) + where project != null + select new ProjectSource(project, projectData.ToImmutableArray())); + + return sources.ToImmutable(); + } +} diff --git a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueSessionState.cs b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueSessionState.cs new file mode 100644 index 0000000000000..b98a63d9b3337 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueSessionState.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.EditAndContinue; + +/// +/// Manages in-proc EnC and Hot Reload session state that other components need to keep track of. +/// +/// +/// Separated from the in-proc EnC language service to allow access from lower-layer components. +/// +/// provides read-only access, +/// provides read-write access, which is only used by the EnC language service. +/// +[Export(typeof(IEditAndContinueSessionTracker))] +[Export(typeof(EditAndContinueSessionState))] +[Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class EditAndContinueSessionState() : IEditAndContinueSessionTracker +{ + /// + /// Set to true when EnC or Hot Reload session becomes active (e.g. F5/Ctrl+F5), to false when it ends. + /// + public bool IsSessionActive { get; set; } + + /// + /// Updated when the user attempts to apply changes. + /// Includes EnC emit diagnostics and debuggee state related diagnostics. + /// + public ImmutableArray ApplyChangesDiagnostics { get; set; } = []; +} diff --git a/src/Features/LanguageServer/Protocol/Features/Options/ClassificationOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/ClassificationOptionsStorage.cs index 87d9faa3241fd..194765e18320e 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/ClassificationOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/ClassificationOptionsStorage.cs @@ -12,6 +12,7 @@ public static ClassificationOptions GetClassificationOptions(this IOptionsReader => new() { ClassifyReassignedVariables = globalOptions.GetOption(ClassifyReassignedVariables, language), + ClassifyObsoleteSymbols = globalOptions.GetOption(ClassifyObsoleteSymbols, language), ColorizeRegexPatterns = globalOptions.GetOption(ColorizeRegexPatterns, language), ColorizeJsonPatterns = globalOptions.GetOption(ColorizeJsonPatterns, language), // ForceFrozenPartialSemanticsForCrossProcessOperations not stored in global options @@ -23,6 +24,9 @@ public static OptionsProvider GetClassificationOptionsPro public static PerLanguageOption2 ClassifyReassignedVariables = new("dotnet_classify_reassigned_variables", ClassificationOptions.Default.ClassifyReassignedVariables); + public static PerLanguageOption2 ClassifyObsoleteSymbols = + new("dotnet_classify_obsolete_symbols", ClassificationOptions.Default.ClassifyObsoleteSymbols); + public static PerLanguageOption2 ColorizeRegexPatterns = new("dotnet_colorize_regex_patterns", ClassificationOptions.Default.ColorizeRegexPatterns); diff --git a/src/Features/LanguageServer/Protocol/Features/Options/DiagnosticOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/DiagnosticOptionsStorage.cs index 58fe92e6f27b4..f43204d5ab3ab 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/DiagnosticOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/DiagnosticOptionsStorage.cs @@ -8,9 +8,6 @@ namespace Microsoft.CodeAnalysis.Diagnostics; internal sealed class DiagnosticOptionsStorage { - public static readonly Option2 PullDiagnosticsFeatureFlag = new( - "dotnet_enable_pull_diagnostics", defaultValue: true); - public static readonly Option2 LogTelemetryForBackgroundAnalyzerExecution = new( "dotnet_log_telemetry_for_background_analyzer_execution", defaultValue: false); diff --git a/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs index 8aacbe48a42fe..cc7fc24bc3875 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; diff --git a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs index df4a8ed19d97e..773b736d0cb20 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Storage; @@ -13,9 +12,14 @@ internal static class WorkspaceConfigurationOptionsStorage public static WorkspaceConfigurationOptions GetWorkspaceConfigurationOptions(this IGlobalOptionService globalOptions) => new( CacheStorage: globalOptions.GetOption(Database), - EnableOpeningSourceGeneratedFiles: globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspace) ?? - globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag), + EnableOpeningSourceGeneratedFiles: + globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspace) ?? + globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag), DisableRecoverableText: globalOptions.GetOption(DisableRecoverableText), + SourceGeneratorExecution: + globalOptions.GetOption(SourceGeneratorExecution) ?? + (globalOptions.GetOption(SourceGeneratorExecutionBalancedFeatureFlag) ? SourceGeneratorExecutionPreference.Balanced : SourceGeneratorExecutionPreference.Automatic), + ValidateCompilationTrackerStates: globalOptions.GetOption(ValidateCompilationTrackerStates)); public static readonly Option2 Database = new( @@ -36,4 +40,15 @@ public static WorkspaceConfigurationOptions GetWorkspaceConfigurationOptions(thi public static readonly Option2 EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag = new( "dotnet_enable_opening_source_generated_files_in_workspace_feature_flag", WorkspaceConfigurationOptions.Default.EnableOpeningSourceGeneratedFiles); + + public static readonly Option2 SourceGeneratorExecution = new( + "dotnet_source_generator_execution", + defaultValue: null, + isEditorConfigOption: true, + serializer: new EditorConfigValueSerializer( + s => SourceGeneratorExecutionPreferenceUtilities.Parse(s), + SourceGeneratorExecutionPreferenceUtilities.GetEditorConfigString)); + + public static readonly Option2 SourceGeneratorExecutionBalancedFeatureFlag = new( + "dotnet_source_generator_execution_balanced_feature_flag", false); } diff --git a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs index 75513230f0080..cf1b8f407a589 100644 --- a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs +++ b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs @@ -148,7 +148,7 @@ async Task GetUnifiedSuggestedActionAsync(Solution orig { if (action.NestedActions.Length > 0) { - using var _ = ArrayBuilder.GetInstance(action.NestedActions.Length, out var unifiedNestedActions); + var unifiedNestedActions = new FixedSizeArrayBuilder(action.NestedActions.Length); foreach (var nestedAction in action.NestedActions) { var unifiedNestedAction = await GetUnifiedSuggestedActionAsync(originalSolution, nestedAction, fix).ConfigureAwait(false); @@ -158,7 +158,7 @@ async Task GetUnifiedSuggestedActionAsync(Solution orig var set = new UnifiedSuggestedActionSet( originalSolution, categoryName: null, - actions: unifiedNestedActions.ToImmutableAndClear(), + actions: unifiedNestedActions.MoveToImmutable(), title: null, priority: action.Priority, applicableToSpan: fix.PrimaryDiagnostic.Location.SourceSpan); @@ -401,7 +401,7 @@ private static void AddUnifiedSuggestedActionsSet( sets.Add(new UnifiedSuggestedActionSet( originalSolution, category, - group.ToImmutableArray(), + [.. group], title: null, priority, applicableToSpan: groupKey.Item1.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text))); @@ -454,14 +454,14 @@ public static async Task> GetFilterAnd var filteredRefactorings = FilterOnAnyThread(refactorings, selection, filterOutsideSelection); - using var _ = ArrayBuilder.GetInstance(filteredRefactorings.Length, out var orderedRefactorings); + var orderedRefactorings = new FixedSizeArrayBuilder(filteredRefactorings.Length); foreach (var refactoring in filteredRefactorings) { var orderedRefactoring = await OrganizeRefactoringsAsync(workspace, document, selection, refactoring, cancellationToken).ConfigureAwait(false); orderedRefactorings.Add(orderedRefactoring); } - return orderedRefactorings.ToImmutableAndClear(); + return orderedRefactorings.MoveToImmutable(); } private static ImmutableArray FilterOnAnyThread( @@ -546,7 +546,7 @@ async Task GetUnifiedSuggestedActionSetAsync(CodeAction { if (codeAction.NestedActions.Length > 0) { - using var _1 = ArrayBuilder.GetInstance(codeAction.NestedActions.Length, out var nestedActions); + var nestedActions = new FixedSizeArrayBuilder(codeAction.NestedActions.Length); foreach (var nestedAction in codeAction.NestedActions) { var unifiedAction = await GetUnifiedSuggestedActionSetAsync(nestedAction, applicableToSpan, selection, cancellationToken).ConfigureAwait(false); @@ -556,7 +556,7 @@ async Task GetUnifiedSuggestedActionSetAsync(CodeAction var set = new UnifiedSuggestedActionSet( originalSolution, categoryName: null, - actions: nestedActions.ToImmutableAndClear(), + actions: nestedActions.MoveToImmutable(), title: null, priority: codeAction.Priority, applicableToSpan: applicableToSpan); @@ -711,9 +711,7 @@ private static ImmutableArray GetInitiallyOrderedActi private static ImmutableArray OrderActionSets( ImmutableArray actionSets, TextSpan? selectionOpt) { - return actionSets.OrderByDescending(s => s.Priority) - .ThenBy(s => s, new UnifiedSuggestedActionSetComparer(selectionOpt)) - .ToImmutableArray(); + return [.. actionSets.OrderByDescending(s => s.Priority).ThenBy(s => s, new UnifiedSuggestedActionSetComparer(selectionOpt))]; } private static UnifiedSuggestedActionSet WithPriority( @@ -774,7 +772,7 @@ private static ImmutableArray FilterActionSetsByTitle } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static UnifiedSuggestedActionSet? FilterActionSetByTitle(UnifiedSuggestedActionSet set, HashSet seenTitles) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs index a0fa0af6eef3a..44136ac1f6479 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs @@ -124,12 +124,12 @@ private static LSP.CodeAction[] GenerateCodeActions( { CommandIdentifier = CodeActionsHandler.RunNestedCodeActionCommandName, Title = title, - Arguments = [new CodeActionResolveData(title, codeAction.CustomTags, request.Range, request.TextDocument, codeActionPathList.ToArray(), fixAllFlavors: null, nestedCodeActions: nestedCodeActions)] + Arguments = [new CodeActionResolveData(title, codeAction.CustomTags, request.Range, request.TextDocument, [.. codeActionPathList], fixAllFlavors: null, nestedCodeActions: nestedCodeActions)] }; } AddLSPCodeActions(builder, codeAction, request, codeActionKind, diagnosticsForFix, nestedCodeActionCommand, - nestedCodeActions, codeActionPathList.ToArray(), suggestedAction); + nestedCodeActions, [.. codeActionPathList], suggestedAction); return builder.ToArray(); } @@ -161,11 +161,11 @@ private static LSP.CodeAction[] GenerateCodeActions( if (!isTopLevelCodeAction) { AddLSPCodeActions(nestedCodeActions, codeAction, request, codeActionKind, diagnosticsForFix, - nestedCodeActionCommand: null, nestedCodeActions: null, pathOfParentAction.ToArray(), suggestedAction); + nestedCodeActionCommand: null, nestedCodeActions: null, [.. pathOfParentAction], suggestedAction); } } - return nestedCodeActions.ToImmutable(); + return nestedCodeActions.ToImmutableAndClear(); } private static void AddLSPCodeActions( @@ -240,7 +240,7 @@ private static VSInternalCodeAction GenerateVSCodeAction( Priority = UnifiedSuggestedActionSetPriorityToPriorityLevel(setPriority), Group = $"Roslyn{currentSetNumber}", ApplicableRange = applicableRange, - Data = new CodeActionResolveData(codeAction.Title, codeAction.CustomTags, request.Range, request.TextDocument, fixAllFlavors: null, nestedCodeActions: null, codeActionPath: codeActionPathList.ToArray()) + Data = new CodeActionResolveData(codeAction.Title, codeAction.CustomTags, request.Range, request.TextDocument, fixAllFlavors: null, nestedCodeActions: null, codeActionPath: [.. codeActionPathList]) }; static VSInternalCodeAction[] GenerateNestedVSCodeActions( @@ -338,7 +338,7 @@ public static async Task> GetCodeActionsAsync( } } - return codeActions.ToImmutable(); + return codeActions.ToImmutableAndClear(); } /// diff --git a/src/Features/LanguageServer/Protocol/Handler/Completion/AbstractLspCompletionResultCreationService.cs b/src/Features/LanguageServer/Protocol/Handler/Completion/AbstractLspCompletionResultCreationService.cs index 8cdfd83df2e1f..7f3ceaa0a5795 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Completion/AbstractLspCompletionResultCreationService.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Completion/AbstractLspCompletionResultCreationService.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -121,6 +122,7 @@ internal abstract class AbstractLspCompletionResultCreationService : ILspComplet lspItem.FilterText = item.FilterText; lspItem.Kind = GetCompletionKind(item.Tags, capabilityHelper.SupportedItemKinds); + lspItem.Tags = GetCompletionTags(item.Tags, capabilityHelper.SupportedItemTags); lspItem.Preselect = item.Rules.MatchPriority == MatchPriority.Preselect; if (lspVSClientCapability) @@ -179,6 +181,37 @@ static LSP.CompletionItemKind GetCompletionKind( return LSP.CompletionItemKind.Text; } + static LSP.CompletionItemTag[]? GetCompletionTags( + ImmutableArray tags, + ISet supportedClientTags) + { + using var result = TemporaryArray.Empty; + + foreach (var tag in tags) + { + if (ProtocolConversions.RoslynTagToCompletionItemTags.TryGetValue(tag, out var completionItemTags)) + { + // Always at least pick the core tag provided. + var lspTag = completionItemTags[0]; + + // If better kinds are preferred, return them if the client supports them. + for (var i = 1; i < completionItemTags.Length; i++) + { + var preferredTag = completionItemTags[i]; + if (supportedClientTags.Contains(preferredTag)) + lspTag = preferredTag; + } + + result.Add(lspTag); + } + } + + if (result.Count == 0) + return null; + + return [.. result.ToImmutableAndClear()]; + } + static string[] GetCommitCharacters( CompletionItem item, Dictionary, string[]> currentRuleCache) diff --git a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs index bd565c39807ac..7bf5905039f3d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs @@ -30,6 +30,7 @@ internal sealed class CompletionCapabilityHelper public bool SupportSnippets { get; } public bool SupportsMarkdownDocumentation { get; } public ISet SupportedItemKinds { get; } + public ISet SupportedItemTags { get; } public CompletionCapabilityHelper(ClientCapabilities clientCapabilities) { @@ -42,6 +43,7 @@ public CompletionCapabilityHelper(ClientCapabilities clientCapabilities) SupportCompletionListData = _completionSetting?.CompletionListSetting?.ItemDefaults?.Contains(DataPropertyName) == true; SupportDefaultCommitCharacters = _completionSetting?.CompletionListSetting?.ItemDefaults?.Contains(CommitCharactersPropertyName) == true; SupportedItemKinds = _completionSetting?.CompletionItemKind?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet(); + SupportedItemTags = _completionSetting?.CompletionItem?.TagSupport?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet(); // internal VS LSP if (clientCapabilities.HasVisualStudioLspCapability()) diff --git a/src/Features/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs index bdff74a62be0a..c61ec8ca969a1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs @@ -152,7 +152,7 @@ private async Task> GetConfigurationsAsync(CancellationTo } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } /// @@ -185,7 +185,7 @@ private static ImmutableArray GenerateGlobalConfigurationItem } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } /// diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs new file mode 100644 index 0000000000000..106d2dc6bfc78 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.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; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CommonLanguageServerProtocol.Framework; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractDocumentPullDiagnosticHandler( + IDiagnosticAnalyzerService diagnosticAnalyzerService, + IDiagnosticsRefresher diagnosticRefresher, + IDiagnosticSourceManager diagnosticSourceManager, + IGlobalOptionService globalOptions) + : AbstractPullDiagnosticHandler( + diagnosticAnalyzerService, + diagnosticRefresher, + globalOptions), ITextDocumentIdentifierHandler + where TDiagnosticsParams : IPartialResultParams +{ + protected readonly IDiagnosticSourceManager DiagnosticSourceManager = diagnosticSourceManager; + + public abstract TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); + + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) + { + // Note: context.Document may be null in the case where the client is asking about a document that we have + // since removed from the workspace. In this case, we don't really have anything to process. + // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. + // + // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each + // handler treats those as separate worlds that they are responsible for. + var textDocument = context.TextDocument; + if (textDocument is null) + { + context.TraceInformation("Ignoring diagnostics request because no text document was provided"); + return new([]); + } + + if (!context.IsTracking(textDocument.GetURI())) + { + context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); + return new([]); + } + + return DiagnosticSourceManager.CreateDocumentDiagnosticSourcesAsync(context, requestDiagnosticCategory, cancellationToken); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 19556203d19d7..94bb3f537e1c9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -20,554 +19,558 @@ using Roslyn.Utilities; using LSP = Roslyn.LanguageServer.Protocol; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +/// +/// Root type for both document and workspace diagnostic pull requests. +/// +/// The LSP input param type +/// The LSP type that is reported via IProgress +/// The LSP type that is returned on completion of the request. +internal abstract partial class AbstractPullDiagnosticHandler : ILspServiceRequestHandler + where TDiagnosticsParams : IPartialResultParams { - internal abstract class AbstractDocumentPullDiagnosticHandler( + /// + /// Special value we use to designate workspace diagnostics vs document diagnostics. Document diagnostics + /// should always a workspace diagnostic as the former are 'live' + /// while the latter are cached and may be stale. + /// + protected const int WorkspaceDiagnosticIdentifier = 1; + protected const int DocumentDiagnosticIdentifier = 2; + + private readonly IDiagnosticsRefresher _diagnosticRefresher; + protected readonly IGlobalOptionService GlobalOptions; + + protected readonly IDiagnosticAnalyzerService DiagnosticAnalyzerService; + + /// + /// Cache where we store the data produced by prior requests so that they can be returned if nothing of significance + /// changed. The is produced by while the + /// is produced by . The former is faster + /// and works well for us in the normal case. The latter still allows us to reuse diagnostics when changes happen that + /// update the version stamp but not the content (for example, forking LSP text). + /// + private readonly ConcurrentDictionary> _categoryToVersionedCache = []; + + public bool MutatesSolutionState => false; + public bool RequiresLSPSolution => true; + + protected AbstractPullDiagnosticHandler( IDiagnosticAnalyzerService diagnosticAnalyzerService, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) - : AbstractPullDiagnosticHandler( - diagnosticAnalyzerService, - diagnosticRefresher, - globalOptions), ITextDocumentIdentifierHandler - where TDiagnosticsParams : IPartialResultParams { - public abstract LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); + DiagnosticAnalyzerService = diagnosticAnalyzerService; + _diagnosticRefresher = diagnosticRefresher; + GlobalOptions = globalOptions; } /// - /// Root type for both document and workspace diagnostic pull requests. + /// Retrieve the previous results we reported. Used so we can avoid resending data for unchanged files. Also + /// used so we can report which documents were removed and can have all their diagnostics cleared. + /// + protected abstract ImmutableArray? GetPreviousResults(TDiagnosticsParams diagnosticsParams); + + /// + /// Returns all the documents that should be processed in the desired order to process them in. + /// + protected abstract ValueTask> GetOrderedDiagnosticSourcesAsync( + TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken); + + /// + /// Creates the appropriate LSP type to report a new set of diagnostics and resultId. + /// + protected abstract TReport CreateReport(TextDocumentIdentifier identifier, LSP.Diagnostic[] diagnostics, string resultId); + + /// + /// Creates the appropriate LSP type to report unchanged diagnostics. Can return to + /// indicate nothing should be reported. This should be done for workspace requests to avoiding sending a huge + /// amount of "nothing changed" responses for most files. + /// + protected abstract bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out TReport? report); + + /// + /// Creates the appropriate LSP type to report a removed file. /// - /// The LSP input param type - /// The LSP type that is reported via IProgress - /// The LSP type that is returned on completion of the request. - internal abstract partial class AbstractPullDiagnosticHandler : ILspServiceRequestHandler - where TDiagnosticsParams : IPartialResultParams + protected abstract TReport CreateRemovedReport(TextDocumentIdentifier identifier); + + protected abstract TReturn? CreateReturn(BufferedProgress progress); + + /// + /// Generate the right diagnostic tags for a particular diagnostic. + /// + protected abstract DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource); + + protected abstract string? GetRequestDiagnosticCategory(TDiagnosticsParams diagnosticsParams); + + /// + /// Used by public workspace pull diagnostics to allow it to keep the connection open until + /// changes occur to avoid the client spamming the server with requests. + /// + protected virtual Task WaitForChangesAsync(RequestContext context, CancellationToken cancellationToken) { - /// - /// Special value we use to designate workspace diagnostics vs document diagnostics. Document diagnostics - /// should always a workspace diagnostic as the former are 'live' - /// while the latter are cached and may be stale. - /// - protected const int WorkspaceDiagnosticIdentifier = 1; - protected const int DocumentDiagnosticIdentifier = 2; - // internal for testing purposes - internal const int DocumentNonLocalDiagnosticIdentifier = 3; - - private readonly IDiagnosticsRefresher _diagnosticRefresher; - protected readonly IGlobalOptionService GlobalOptions; - - protected readonly IDiagnosticAnalyzerService DiagnosticAnalyzerService; - - /// - /// Cache where we store the data produced by prior requests so that they can be returned if nothing of significance - /// changed. The is produced by while the - /// is produced by . The former is faster - /// and works well for us in the normal case. The latter still allows us to reuse diagnostics when changes happen that - /// update the version stamp but not the content (for example, forking LSP text). - /// - private readonly ConcurrentDictionary> _categoryToVersionedCache = []; - - public bool MutatesSolutionState => false; - public bool RequiresLSPSolution => true; - - protected AbstractPullDiagnosticHandler( - IDiagnosticAnalyzerService diagnosticAnalyzerService, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - { - DiagnosticAnalyzerService = diagnosticAnalyzerService; - _diagnosticRefresher = diagnosticRefresher; - GlobalOptions = globalOptions; - } + return Task.CompletedTask; + } - protected virtual string? GetDiagnosticSourceIdentifier(TDiagnosticsParams diagnosticsParams) => null; - - /// - /// Retrieve the previous results we reported. Used so we can avoid resending data for unchanged files. Also - /// used so we can report which documents were removed and can have all their diagnostics cleared. - /// - protected abstract ImmutableArray? GetPreviousResults(TDiagnosticsParams diagnosticsParams); - - /// - /// Returns all the documents that should be processed in the desired order to process them in. - /// - protected abstract ValueTask> GetOrderedDiagnosticSourcesAsync( - TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken); - - /// - /// Creates the appropriate LSP type to report a new set of diagnostics and resultId. - /// - protected abstract TReport CreateReport(TextDocumentIdentifier identifier, LSP.Diagnostic[] diagnostics, string resultId); - - /// - /// Creates the appropriate LSP type to report unchanged diagnostics. Can return to - /// indicate nothing should be reported. This should be done for workspace requests to avoiding sending a huge - /// amount of "nothing changed" responses for most files. - /// - protected abstract bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out TReport? report); - - /// - /// Creates the appropriate LSP type to report a removed file. - /// - protected abstract TReport CreateRemovedReport(TextDocumentIdentifier identifier); - - protected abstract TReturn? CreateReturn(BufferedProgress progress); - - /// - /// Generate the right diagnostic tags for a particular diagnostic. - /// - protected abstract DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource); - - protected abstract string? GetDiagnosticCategory(TDiagnosticsParams diagnosticsParams); - - /// - /// Used by public workspace pull diagnostics to allow it to keep the connection open until - /// changes occur to avoid the client spamming the server with requests. - /// - protected virtual Task WaitForChangesAsync(RequestContext context, CancellationToken cancellationToken) + public async Task HandleRequestAsync( + TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) + { + // The progress object we will stream reports to. + using var progress = BufferedProgress.Create(diagnosticsParams.PartialResultToken); + + // We only support this option to disable crawling in internal speedometer and ddrit perf runs to lower + // noise. It is not exposed to the user. + if (!this.GlobalOptions.GetOption(SolutionCrawlerRegistrationService.EnableSolutionCrawler)) { - return Task.CompletedTask; + context.TraceInformation($"{this.GetType()}. Skipping due to {nameof(SolutionCrawlerRegistrationService.EnableSolutionCrawler)}={false}"); } - - public async Task HandleRequestAsync( - TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) + else { - // The progress object we will stream reports to. - using var progress = BufferedProgress.Create(diagnosticsParams.PartialResultToken); + Contract.ThrowIfNull(context.Solution); - // We only support this option to disable crawling in internal speedometer and ddrit perf runs to lower - // noise. It is not exposed to the user. - if (!this.GlobalOptions.GetOption(SolutionCrawlerRegistrationService.EnableSolutionCrawler)) - { - context.TraceInformation($"{this.GetType()}. Skipping due to {nameof(SolutionCrawlerRegistrationService.EnableSolutionCrawler)}={false}"); - } - else - { - var clientCapabilities = context.GetRequiredClientCapabilities(); - var category = GetDiagnosticCategory(diagnosticsParams) ?? ""; - var sourceIdentifier = GetDiagnosticSourceIdentifier(diagnosticsParams) ?? ""; - var handlerName = $"{this.GetType().Name}(category: {category}, source: {sourceIdentifier})"; - context.TraceInformation($"{handlerName} started getting diagnostics"); + var clientCapabilities = context.GetRequiredClientCapabilities(); + var category = GetRequestDiagnosticCategory(diagnosticsParams); + var handlerName = $"{this.GetType().Name}(category: {category})"; + context.TraceInformation($"{handlerName} started getting diagnostics"); + + var versionedCache = _categoryToVersionedCache.GetOrAdd(handlerName, static handlerName => new(handlerName)); - var versionedCache = _categoryToVersionedCache.GetOrAdd(handlerName, static handlerName => new(handlerName)); + // Get the set of results the request said were previously reported. We can use this to determine both + // what to skip, and what files we have to tell the client have been removed. + var previousResults = GetPreviousResults(diagnosticsParams) ?? []; + context.TraceInformation($"previousResults.Length={previousResults.Length}"); - // Get the set of results the request said were previously reported. We can use this to determine both - // what to skip, and what files we have to tell the client have been removed. - var previousResults = GetPreviousResults(diagnosticsParams) ?? []; - context.TraceInformation($"previousResults.Length={previousResults.Length}"); + // Create a mapping from documents to the previous results the client says it has for them. That way as we + // process documents we know if we should tell the client it should stay the same, or we can tell it what + // the updated diagnostics are. + using var _1 = PooledDictionary.GetInstance(out var documentIdToPreviousDiagnosticParams); + using var _2 = PooledHashSet.GetInstance(out var removedDocuments); + ProcessPreviousResults(context.Solution, previousResults, documentIdToPreviousDiagnosticParams, removedDocuments); - // Create a mapping from documents to the previous results the client says it has for them. That way as we - // process documents we know if we should tell the client it should stay the same, or we can tell it what - // the updated diagnostics are. - var documentToPreviousDiagnosticParams = GetIdToPreviousDiagnosticParams(context, previousResults, out var removedResults); + // First, let the client know if any workspace documents have gone away. That way it can remove those for + // the user from squiggles or error-list. + HandleRemovedDocuments(context, removedDocuments, progress); - // First, let the client know if any workspace documents have gone away. That way it can remove those for - // the user from squiggles or error-list. - HandleRemovedDocuments(context, removedResults, progress); + // Next process each file in priority order. Determine if diagnostics are changed or unchanged since the + // last time we notified the client. Report back either to the client so they can update accordingly. + var orderedSources = await GetOrderedDiagnosticSourcesAsync( + diagnosticsParams, category, context, cancellationToken).ConfigureAwait(false); - // Next process each file in priority order. Determine if diagnostics are changed or unchanged since the - // last time we notified the client. Report back either to the client so they can update accordingly. - var orderedSources = await GetOrderedDiagnosticSourcesAsync( - diagnosticsParams, context, cancellationToken).ConfigureAwait(false); + context.TraceInformation($"Processing {orderedSources.Length} documents"); - context.TraceInformation($"Processing {orderedSources.Length} documents"); + // Keep track of what diagnostic sources we see this time around. For any we do not see this time + // around, we'll notify the client that the diagnostics for it have been removed. + using var _3 = PooledHashSet.GetInstance(out var seenDiagnosticSourceIds); - foreach (var diagnosticSource in orderedSources) + foreach (var diagnosticSource in orderedSources) + { + seenDiagnosticSourceIds.Add(diagnosticSource.GetId()); + var globalStateVersion = _diagnosticRefresher.GlobalStateVersion; + + var project = diagnosticSource.GetProject(); + + var newResultId = await versionedCache.GetNewResultIdAsync( + documentIdToPreviousDiagnosticParams, + diagnosticSource.GetId(), + project, + computeCheapVersionAsync: async () => (globalStateVersion, await project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false)), + computeExpensiveVersionAsync: async () => (globalStateVersion, await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false)), + cancellationToken).ConfigureAwait(false); + if (newResultId != null) { - var globalStateVersion = _diagnosticRefresher.GlobalStateVersion; - - var project = diagnosticSource.GetProject(); - - var newResultId = await versionedCache.GetNewResultIdAsync( - documentToPreviousDiagnosticParams, - diagnosticSource.GetId(), - project, - computeCheapVersionAsync: async () => (globalStateVersion, await project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false)), - computeExpensiveVersionAsync: async () => (globalStateVersion, await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false)), - cancellationToken).ConfigureAwait(false); - if (newResultId != null) - { - await ComputeAndReportCurrentDiagnosticsAsync( - context, diagnosticSource, progress, newResultId, clientCapabilities, cancellationToken).ConfigureAwait(false); - } - else - { - context.TraceInformation($"Diagnostics were unchanged for {diagnosticSource.ToDisplayString()}"); - - // Nothing changed between the last request and this one. Report a (null-diagnostics, - // same-result-id) response to the client as that means they should just preserve the current - // diagnostics they have for this file. - // - // Note: if this is a workspace request, we can do nothing, as that will be interpreted by the - // client as nothing having been changed for that document. - var previousParams = documentToPreviousDiagnosticParams[diagnosticSource.GetId()]; - if (TryCreateUnchangedReport(previousParams.TextDocument, previousParams.PreviousResultId, out var report)) - progress.Report(report); - } + await ComputeAndReportCurrentDiagnosticsAsync( + context, diagnosticSource, progress, newResultId, clientCapabilities, cancellationToken).ConfigureAwait(false); } + else + { + context.TraceInformation($"Diagnostics were unchanged for {diagnosticSource.ToDisplayString()}"); - // Clear out the solution context to avoid retaining memory - // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1809058 - context.ClearSolutionContext(); - - // Some implementations of the spec will re-open requests as soon as we close them, spamming the server. - // In those cases, we wait for the implementation to indicate that changes have occurred, then we close the connection - // so that the client asks us again. - await WaitForChangesAsync(context, cancellationToken).ConfigureAwait(false); + // Nothing changed between the last request and this one. Report a (null-diagnostics, + // same-result-id) response to the client as that means they should just preserve the current + // diagnostics they have for this file. + // + // Note: if this is a workspace request, we can do nothing, as that will be interpreted by the + // client as nothing having been changed for that document. + var previousParams = documentIdToPreviousDiagnosticParams[diagnosticSource.GetId()]; + if (TryCreateUnchangedReport(previousParams.TextDocument, previousParams.PreviousResultId, out var report)) + progress.Report(report); + } + } - // If we had a progress object, then we will have been reporting to that. Otherwise, take what we've been - // collecting and return that. - context.TraceInformation($"{this.GetType()} finished getting diagnostics"); + // Now, for any diagnostics reported from a prior source that we do not see this time around, report its + // diagnostics as being removed. This allows for different sets of diagnostic-sources to be computed + // each time around, while still producing accurate diagnostic reports. + // + // Only do this if we haven't already created a removal report for that prior result above. + // + // Note: we are intentionally notifying the client that this is not a remove (vs an empty set of + // results). As far as we and the client are concerned, this document no longer exists at this point + // for the purposes of diagnostics. + foreach (var (projectOrDocumentId, previousDiagnosticParams) in documentIdToPreviousDiagnosticParams) + { + if (!seenDiagnosticSourceIds.Contains(projectOrDocumentId) && + !removedDocuments.Contains(previousDiagnosticParams)) + { + progress.Report(CreateRemovedReport(previousDiagnosticParams.TextDocument)); + } } - return CreateReturn(progress); + // Clear out the solution context to avoid retaining memory + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1809058 + context.ClearSolutionContext(); + + // Some implementations of the spec will re-open requests as soon as we close them, spamming the server. + // In those cases, we wait for the implementation to indicate that changes have occurred, then we close the connection + // so that the client asks us again. + await WaitForChangesAsync(context, cancellationToken).ConfigureAwait(false); + + // If we had a progress object, then we will have been reporting to that. Otherwise, take what we've been + // collecting and return that. + context.TraceInformation($"{this.GetType()} finished getting diagnostics"); } - private static Dictionary GetIdToPreviousDiagnosticParams( - RequestContext context, ImmutableArray previousResults, out ImmutableArray removedDocuments) - { - Contract.ThrowIfNull(context.Solution); + return CreateReturn(progress); - var result = new Dictionary(); - using var _ = ArrayBuilder.GetInstance(out var removedDocumentsBuilder); + static void ProcessPreviousResults( + Solution solution, + ImmutableArray previousResults, + Dictionary idToPreviousDiagnosticParams, + HashSet removedResults) + { foreach (var diagnosticParams in previousResults) { if (diagnosticParams.TextDocument != null) { - var id = GetIdForPreviousResult(diagnosticParams.TextDocument, context.Solution); + var id = GetIdForPreviousResult(diagnosticParams.TextDocument, solution); if (id != null) { - result[id.Value] = diagnosticParams; + idToPreviousDiagnosticParams[id.Value] = diagnosticParams; } else { // The client previously had a result from us for this document, but we no longer have it in our solution. // Record it so we can report to the client that it has been removed. - removedDocumentsBuilder.Add(diagnosticParams); + removedResults.Add(diagnosticParams); } } } + } - removedDocuments = removedDocumentsBuilder.ToImmutable(); - return result; - - static ProjectOrDocumentId? GetIdForPreviousResult(TextDocumentIdentifier textDocumentIdentifier, Solution solution) + static ProjectOrDocumentId? GetIdForPreviousResult(TextDocumentIdentifier textDocumentIdentifier, Solution solution) + { + var document = solution.GetDocument(textDocumentIdentifier); + if (document != null) { - var document = solution.GetDocument(textDocumentIdentifier); - if (document != null) - { - return new ProjectOrDocumentId(document.Id); - } - - var project = solution.GetProject(textDocumentIdentifier); - if (project != null) - { - return new ProjectOrDocumentId(project.Id); - } + return new ProjectOrDocumentId(document.Id); + } - var additionalDocument = solution.GetAdditionalDocument(textDocumentIdentifier); - if (additionalDocument != null) - { - return new ProjectOrDocumentId(additionalDocument.Id); - } + var project = solution.GetProject(textDocumentIdentifier); + if (project != null) + { + return new ProjectOrDocumentId(project.Id); + } - return null; + var additionalDocument = solution.GetAdditionalDocument(textDocumentIdentifier); + if (additionalDocument != null) + { + return new ProjectOrDocumentId(additionalDocument.Id); } + + return null; } + } - private async Task ComputeAndReportCurrentDiagnosticsAsync( - RequestContext context, - IDiagnosticSource diagnosticSource, - BufferedProgress progress, - string resultId, - ClientCapabilities clientCapabilities, - CancellationToken cancellationToken) + private async Task ComputeAndReportCurrentDiagnosticsAsync( + RequestContext context, + IDiagnosticSource diagnosticSource, + BufferedProgress progress, + string resultId, + ClientCapabilities clientCapabilities, + CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var result); + var diagnostics = await diagnosticSource.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); + + // If we can't get a text document identifier we can't report diagnostics for this source. + // This can happen for 'fake' projects (e.g. used for TS script blocks). + var documentIdentifier = diagnosticSource.GetDocumentIdentifier(); + if (documentIdentifier == null) { - using var _ = ArrayBuilder.GetInstance(out var result); - var diagnostics = await diagnosticSource.GetDiagnosticsAsync(DiagnosticAnalyzerService, context, cancellationToken).ConfigureAwait(false); + // We are not expecting to get any diagnostics for sources that don't have a path. + Contract.ThrowIfFalse(diagnostics.IsEmpty); + return; + } - // If we can't get a text document identifier we can't report diagnostics for this source. - // This can happen for 'fake' projects (e.g. used for TS script blocks). - var documentIdentifier = diagnosticSource.GetDocumentIdentifier(); - if (documentIdentifier == null) - { - // We are not expecting to get any diagnostics for sources that don't have a path. - Contract.ThrowIfFalse(diagnostics.IsEmpty); - return; - } + context.TraceInformation($"Found {diagnostics.Length} diagnostics for {diagnosticSource.ToDisplayString()}"); - context.TraceInformation($"Found {diagnostics.Length} diagnostics for {diagnosticSource.ToDisplayString()}"); + foreach (var diagnostic in diagnostics) + result.AddRange(ConvertDiagnostic(diagnosticSource, diagnostic, clientCapabilities)); - foreach (var diagnostic in diagnostics) - result.AddRange(ConvertDiagnostic(diagnosticSource, diagnostic, clientCapabilities)); + var report = CreateReport(documentIdentifier, result.ToArray(), resultId); + progress.Report(report); + } - var report = CreateReport(documentIdentifier, result.ToArray(), resultId); - progress.Report(report); + private void HandleRemovedDocuments(RequestContext context, HashSet removedPreviousResults, BufferedProgress progress) + { + foreach (var removedResult in removedPreviousResults) + { + context.TraceInformation($"Clearing diagnostics for removed document: {removedResult.TextDocument.Uri}"); + + // Client is asking server about a document that no longer exists (i.e. was removed/deleted from + // the workspace). Report a (null-diagnostics, null-result-id) response to the client as that + // means they should just consider the file deleted and should remove all diagnostics + // information they've cached for it. + progress.Report(CreateRemovedReport(removedResult.TextDocument)); } + } - private void HandleRemovedDocuments(RequestContext context, ImmutableArray removedPreviousResults, BufferedProgress progress) + private ImmutableArray ConvertDiagnostic(IDiagnosticSource diagnosticSource, DiagnosticData diagnosticData, ClientCapabilities capabilities) + { + if (!ShouldIncludeHiddenDiagnostic(diagnosticData, capabilities)) { - foreach (var removedResult in removedPreviousResults) - { - context.TraceInformation($"Clearing diagnostics for removed document: {removedResult.TextDocument.Uri}"); + return []; + } - // Client is asking server about a document that no longer exists (i.e. was removed/deleted from - // the workspace). Report a (null-diagnostics, null-result-id) response to the client as that - // means they should just consider the file deleted and should remove all diagnostics - // information they've cached for it. - progress.Report(CreateRemovedReport(removedResult.TextDocument)); - } + var project = diagnosticSource.GetProject(); + var diagnostic = CreateLspDiagnostic(diagnosticData, project, capabilities); + + // Check if we need to handle the unnecessary tag (fading). + if (!diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary)) + { + return [diagnostic]; } - private ImmutableArray ConvertDiagnostic(IDiagnosticSource diagnosticSource, DiagnosticData diagnosticData, ClientCapabilities capabilities) + // DiagnosticId supports fading, check if the corresponding VS option is turned on. + if (!SupportsFadingOption(diagnosticData)) { - if (!ShouldIncludeHiddenDiagnostic(diagnosticData, capabilities)) - { - return []; - } + return [diagnostic]; + } - var project = diagnosticSource.GetProject(); - var diagnostic = CreateLspDiagnostic(diagnosticData, project, capabilities); + // Check to see if there are specific locations marked to fade. + if (!diagnosticData.TryGetUnnecessaryDataLocations(out var unnecessaryLocations)) + { + // There are no specific fading locations, just mark the whole diagnostic span as unnecessary. + // We should always have at least one tag (build or intellisense error). + Contract.ThrowIfNull(diagnostic.Tags, $"diagnostic {diagnostic.Identifier} was missing tags"); + diagnostic.Tags = diagnostic.Tags.Append(DiagnosticTag.Unnecessary); + return [diagnostic]; + } - // Check if we need to handle the unnecessary tag (fading). - if (!diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary)) + if (capabilities.HasVisualStudioLspCapability()) + { + // Roslyn produces unnecessary diagnostics by using additional locations, however LSP doesn't support tagging + // additional locations separately. Instead we just create multiple hidden diagnostics for unnecessary squiggling. + using var _ = ArrayBuilder.GetInstance(out var diagnosticsBuilder); + diagnosticsBuilder.Add(diagnostic); + foreach (var location in unnecessaryLocations) { - return [diagnostic]; + var additionalDiagnostic = CreateLspDiagnostic(diagnosticData, project, capabilities); + additionalDiagnostic.Severity = LSP.DiagnosticSeverity.Hint; + additionalDiagnostic.Range = GetRange(location); + additionalDiagnostic.Tags = [DiagnosticTag.Unnecessary, VSDiagnosticTags.HiddenInEditor, VSDiagnosticTags.HiddenInErrorList, VSDiagnosticTags.SuppressEditorToolTip]; + diagnosticsBuilder.Add(additionalDiagnostic); } - // DiagnosticId supports fading, check if the corresponding VS option is turned on. - if (!SupportsFadingOption(diagnosticData)) + return diagnosticsBuilder.ToImmutableArray(); + } + else + { + diagnostic.Tags = diagnostic.Tags != null ? diagnostic.Tags.Append(DiagnosticTag.Unnecessary) : [DiagnosticTag.Unnecessary]; + var diagnosticRelatedInformation = unnecessaryLocations.Value.Select(l => new DiagnosticRelatedInformation { - return [diagnostic]; - } + Location = new LSP.Location + { + Range = GetRange(l), + Uri = ProtocolConversions.CreateAbsoluteUri(l.UnmappedFileSpan.Path) + }, + Message = diagnostic.Message + }).ToArray(); + diagnostic.RelatedInformation = diagnosticRelatedInformation; + return [diagnostic]; + } - // Check to see if there are specific locations marked to fade. - if (!diagnosticData.TryGetUnnecessaryDataLocations(out var unnecessaryLocations)) - { - // There are no specific fading locations, just mark the whole diagnostic span as unnecessary. - // We should always have at least one tag (build or intellisense error). - Contract.ThrowIfNull(diagnostic.Tags, $"diagnostic {diagnostic.Identifier} was missing tags"); - diagnostic.Tags = diagnostic.Tags.Append(DiagnosticTag.Unnecessary); - return [diagnostic]; - } + LSP.VSDiagnostic CreateLspDiagnostic( + DiagnosticData diagnosticData, + Project project, + ClientCapabilities capabilities) + { + Contract.ThrowIfNull(diagnosticData.Message, $"Got a document diagnostic that did not have a {nameof(diagnosticData.Message)}"); - if (capabilities.HasVisualStudioLspCapability()) + // We can just use VSDiagnostic as it doesn't have any default properties set that + // would get automatically serialized. + var diagnostic = new LSP.VSDiagnostic { - // Roslyn produces unnecessary diagnostics by using additional locations, however LSP doesn't support tagging - // additional locations separately. Instead we just create multiple hidden diagnostics for unnecessary squiggling. - using var _ = ArrayBuilder.GetInstance(out var diagnosticsBuilder); - diagnosticsBuilder.Add(diagnostic); - foreach (var location in unnecessaryLocations) - { - var additionalDiagnostic = CreateLspDiagnostic(diagnosticData, project, capabilities); - additionalDiagnostic.Severity = LSP.DiagnosticSeverity.Hint; - additionalDiagnostic.Range = GetRange(location); - additionalDiagnostic.Tags = [DiagnosticTag.Unnecessary, VSDiagnosticTags.HiddenInEditor, VSDiagnosticTags.HiddenInErrorList, VSDiagnosticTags.SuppressEditorToolTip]; - diagnosticsBuilder.Add(additionalDiagnostic); - } + Code = diagnosticData.Id, + CodeDescription = ProtocolConversions.HelpLinkToCodeDescription(diagnosticData.GetValidHelpLinkUri()), + Message = diagnosticData.Message, + Severity = ConvertDiagnosticSeverity(diagnosticData.Severity, capabilities), + Tags = ConvertTags(diagnosticData, diagnosticSource.IsLiveSource()), + DiagnosticRank = ConvertRank(diagnosticData), + Range = GetRange(diagnosticData.DataLocation) + }; - return diagnosticsBuilder.ToImmutableArray(); - } - else + if (capabilities.HasVisualStudioLspCapability()) { - diagnostic.Tags = diagnostic.Tags != null ? diagnostic.Tags.Append(DiagnosticTag.Unnecessary) : [DiagnosticTag.Unnecessary]; - var diagnosticRelatedInformation = unnecessaryLocations.Value.Select(l => new DiagnosticRelatedInformation - { - Location = new LSP.Location + diagnostic.DiagnosticType = diagnosticData.Category; + diagnostic.ExpandedMessage = diagnosticData.Description; + diagnostic.Projects = + [ + new VSDiagnosticProjectInformation { - Range = GetRange(l), - Uri = ProtocolConversions.CreateAbsoluteUri(l.UnmappedFileSpan.Path) + ProjectIdentifier = project.Id.Id.ToString(), + ProjectName = project.Name, }, - Message = diagnostic.Message - }).ToArray(); - diagnostic.RelatedInformation = diagnosticRelatedInformation; - return [diagnostic]; + ]; + + // Defines an identifier used by the client for merging diagnostics across projects. We want diagnostics + // to be merged from separate projects if they have the same code, filepath, range, and message. + // + // Note: LSP pull diagnostics only operates on unmapped locations. + diagnostic.Identifier = (diagnostic.Code, diagnosticData.DataLocation.UnmappedFileSpan.Path, diagnostic.Range, diagnostic.Message) + .GetHashCode().ToString(); } - LSP.VSDiagnostic CreateLspDiagnostic( - DiagnosticData diagnosticData, - Project project, - ClientCapabilities capabilities) - { - Contract.ThrowIfNull(diagnosticData.Message, $"Got a document diagnostic that did not have a {nameof(diagnosticData.Message)}"); + return diagnostic; + } - // We can just use VSDiagnostic as it doesn't have any default properties set that - // would get automatically serialized. - var diagnostic = new LSP.VSDiagnostic + static LSP.Range GetRange(DiagnosticDataLocation dataLocation) + { + // We currently do not map diagnostics spans as + // 1. Razor handles span mapping for razor files on their side. + // 2. LSP does not allow us to report document pull diagnostics for a different file path. + // 3. The VS LSP client does not support document pull diagnostics for files outside our content type. + // 4. This matches classic behavior where we only squiggle the original location anyway. + + // We also do not adjust the diagnostic locations to ensure they are in bounds because we've + // explicitly requested up to date diagnostics as of the snapshot we were passed in. + return new LSP.Range + { + Start = new Position { - Code = diagnosticData.Id, - CodeDescription = ProtocolConversions.HelpLinkToCodeDescription(diagnosticData.GetValidHelpLinkUri()), - Message = diagnosticData.Message, - Severity = ConvertDiagnosticSeverity(diagnosticData.Severity, capabilities), - Tags = ConvertTags(diagnosticData, diagnosticSource.IsLiveSource()), - DiagnosticRank = ConvertRank(diagnosticData), - Range = GetRange(diagnosticData.DataLocation) - }; - - if (capabilities.HasVisualStudioLspCapability()) + Character = dataLocation.UnmappedFileSpan.StartLinePosition.Character, + Line = dataLocation.UnmappedFileSpan.StartLinePosition.Line, + }, + End = new Position { - diagnostic.DiagnosticType = diagnosticData.Category; - diagnostic.ExpandedMessage = diagnosticData.Description; - diagnostic.Projects = - [ - new VSDiagnosticProjectInformation - { - ProjectIdentifier = project.Id.Id.ToString(), - ProjectName = project.Name, - }, - ]; - - // Defines an identifier used by the client for merging diagnostics across projects. We want diagnostics - // to be merged from separate projects if they have the same code, filepath, range, and message. - // - // Note: LSP pull diagnostics only operates on unmapped locations. - diagnostic.Identifier = (diagnostic.Code, diagnosticData.DataLocation.UnmappedFileSpan.Path, diagnostic.Range, diagnostic.Message) - .GetHashCode().ToString(); + Character = dataLocation.UnmappedFileSpan.EndLinePosition.Character, + Line = dataLocation.UnmappedFileSpan.EndLinePosition.Line, } + }; + } - return diagnostic; + static bool ShouldIncludeHiddenDiagnostic(DiagnosticData diagnosticData, ClientCapabilities capabilities) + { + // VS can handle us reporting any kind of diagnostic using VS custom tags. + if (capabilities.HasVisualStudioLspCapability() == true) + { + return true; } - static LSP.Range GetRange(DiagnosticDataLocation dataLocation) + // Diagnostic isn't hidden - we should report this diagnostic in all scenarios. + if (diagnosticData.Severity != DiagnosticSeverity.Hidden) { - // We currently do not map diagnostics spans as - // 1. Razor handles span mapping for razor files on their side. - // 2. LSP does not allow us to report document pull diagnostics for a different file path. - // 3. The VS LSP client does not support document pull diagnostics for files outside our content type. - // 4. This matches classic behavior where we only squiggle the original location anyway. - - // We also do not adjust the diagnostic locations to ensure they are in bounds because we've - // explicitly requested up to date diagnostics as of the snapshot we were passed in. - return new LSP.Range - { - Start = new Position - { - Character = dataLocation.UnmappedFileSpan.StartLinePosition.Character, - Line = dataLocation.UnmappedFileSpan.StartLinePosition.Line, - }, - End = new Position - { - Character = dataLocation.UnmappedFileSpan.EndLinePosition.Character, - Line = dataLocation.UnmappedFileSpan.EndLinePosition.Line, - } - }; + return true; } - static bool ShouldIncludeHiddenDiagnostic(DiagnosticData diagnosticData, ClientCapabilities capabilities) + // Roslyn creates these for example in remove unnecessary imports, see RemoveUnnecessaryImportsConstants.DiagnosticFixableId. + // These aren't meant to be visible in anyway, so we can safely exclude them. + // TODO - We should probably not be creating these as separate diagnostics or have a 'really really' hidden tag. + if (string.IsNullOrEmpty(diagnosticData.Message)) { - // VS can handle us reporting any kind of diagnostic using VS custom tags. - if (capabilities.HasVisualStudioLspCapability() == true) - { - return true; - } - - // Diagnostic isn't hidden - we should report this diagnostic in all scenarios. - if (diagnosticData.Severity != DiagnosticSeverity.Hidden) - { - return true; - } - - // Roslyn creates these for example in remove unnecessary imports, see RemoveUnnecessaryImportsConstants.DiagnosticFixableId. - // These aren't meant to be visible in anyway, so we can safely exclude them. - // TODO - We should probably not be creating these as separate diagnostics or have a 'really really' hidden tag. - if (string.IsNullOrEmpty(diagnosticData.Message)) - { - return false; - } - - // Hidden diagnostics that are unnecessary are visible to the user in the form of fading. - // We can report these diagnostics. - if (diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary)) - { - return true; - } - - // We have a hidden diagnostic that has no fading. This diagnostic can't be visible so don't send it to the client. return false; } - } - private static VSDiagnosticRank? ConvertRank(DiagnosticData diagnosticData) - { - if (diagnosticData.Properties.TryGetValue(PullDiagnosticConstants.Priority, out var priority)) + // Hidden diagnostics that are unnecessary are visible to the user in the form of fading. + // We can report these diagnostics. + if (diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary)) { - return priority switch - { - PullDiagnosticConstants.Low => VSDiagnosticRank.Low, - PullDiagnosticConstants.Medium => VSDiagnosticRank.Default, - PullDiagnosticConstants.High => VSDiagnosticRank.High, - _ => null, - }; + return true; } - return null; + // We have a hidden diagnostic that has no fading. This diagnostic can't be visible so don't send it to the client. + return false; } + } - private static LSP.DiagnosticSeverity ConvertDiagnosticSeverity(DiagnosticSeverity severity, ClientCapabilities clientCapabilities) - => severity switch + private static VSDiagnosticRank? ConvertRank(DiagnosticData diagnosticData) + { + if (diagnosticData.Properties.TryGetValue(PullDiagnosticConstants.Priority, out var priority)) + { + return priority switch { - // Hidden is translated in ConvertTags to pass along appropriate _ms tags - // that will hide the item in a client that knows about those tags. - DiagnosticSeverity.Hidden => LSP.DiagnosticSeverity.Hint, - // VSCode shows information diagnostics as blue squiggles, and hint diagnostics as 3 dots. We prefer the latter rendering so we return hint diagnostics in vscode. - DiagnosticSeverity.Info => clientCapabilities.HasVisualStudioLspCapability() ? LSP.DiagnosticSeverity.Information : LSP.DiagnosticSeverity.Hint, - DiagnosticSeverity.Warning => LSP.DiagnosticSeverity.Warning, - DiagnosticSeverity.Error => LSP.DiagnosticSeverity.Error, - _ => throw ExceptionUtilities.UnexpectedValue(severity), + PullDiagnosticConstants.Low => VSDiagnosticRank.Low, + PullDiagnosticConstants.Medium => VSDiagnosticRank.Default, + PullDiagnosticConstants.High => VSDiagnosticRank.High, + _ => null, }; + } - /// - /// If you make change in this method, please also update the corresponding file in - /// src\VisualStudio\Xaml\Impl\Implementation\LanguageServer\Handler\Diagnostics\AbstractPullDiagnosticHandler.cs - /// - protected static DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource, bool potentialDuplicate) + return null; + } + + private static LSP.DiagnosticSeverity ConvertDiagnosticSeverity(DiagnosticSeverity severity, ClientCapabilities clientCapabilities) + => severity switch { - using var _ = ArrayBuilder.GetInstance(out var result); + // Hidden is translated in ConvertTags to pass along appropriate _ms tags + // that will hide the item in a client that knows about those tags. + DiagnosticSeverity.Hidden => LSP.DiagnosticSeverity.Hint, + // VSCode shows information diagnostics as blue squiggles, and hint diagnostics as 3 dots. We prefer the latter rendering so we return hint diagnostics in vscode. + DiagnosticSeverity.Info => clientCapabilities.HasVisualStudioLspCapability() ? LSP.DiagnosticSeverity.Information : LSP.DiagnosticSeverity.Hint, + DiagnosticSeverity.Warning => LSP.DiagnosticSeverity.Warning, + DiagnosticSeverity.Error => LSP.DiagnosticSeverity.Error, + _ => throw ExceptionUtilities.UnexpectedValue(severity), + }; - if (diagnosticData.Severity == DiagnosticSeverity.Hidden) - { - result.Add(VSDiagnosticTags.HiddenInEditor); - result.Add(VSDiagnosticTags.HiddenInErrorList); - result.Add(VSDiagnosticTags.SuppressEditorToolTip); - } - else - { - result.Add(VSDiagnosticTags.VisibleInErrorList); - } + /// + /// If you make change in this method, please also update the corresponding file in + /// src\VisualStudio\Xaml\Impl\Implementation\LanguageServer\Handler\Diagnostics\AbstractPullDiagnosticHandler.cs + /// + protected static DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource, bool potentialDuplicate) + { + using var _ = ArrayBuilder.GetInstance(out var result); - if (diagnosticData.CustomTags.Contains(PullDiagnosticConstants.TaskItemCustomTag)) - result.Add(VSDiagnosticTags.TaskItem); + if (diagnosticData.Severity == DiagnosticSeverity.Hidden) + { + result.Add(VSDiagnosticTags.HiddenInEditor); + result.Add(VSDiagnosticTags.HiddenInErrorList); + result.Add(VSDiagnosticTags.SuppressEditorToolTip); + } + else + { + result.Add(VSDiagnosticTags.VisibleInErrorList); + } - // Let the host know that these errors represent potentially stale information from the past that should - // be superseded by fresher info. - if (potentialDuplicate) - result.Add(VSDiagnosticTags.PotentialDuplicate); + if (diagnosticData.CustomTags.Contains(PullDiagnosticConstants.TaskItemCustomTag)) + result.Add(VSDiagnosticTags.TaskItem); - // Mark this also as a build error. That way an explicitly kicked off build from a source like CPS can - // override it. - if (!isLiveSource) - result.Add(VSDiagnosticTags.BuildError); + // Let the host know that these errors represent potentially stale information from the past that should + // be superseded by fresher info. + if (potentialDuplicate) + result.Add(VSDiagnosticTags.PotentialDuplicate); - result.Add(diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Build) - ? VSDiagnosticTags.BuildError - : VSDiagnosticTags.IntellisenseError); + // Mark this also as a build error. That way an explicitly kicked off build from a source like CPS can + // override it. + if (!isLiveSource) + result.Add(VSDiagnosticTags.BuildError); - if (diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.EditAndContinue)) - result.Add(VSDiagnosticTags.EditAndContinueError); + result.Add(diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Build) + ? VSDiagnosticTags.BuildError + : VSDiagnosticTags.IntellisenseError); - return result.ToArray(); - } + if (diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.EditAndContinue)) + result.Add(VSDiagnosticTags.EditAndContinueError); - private bool SupportsFadingOption(DiagnosticData diagnosticData) - { - if (IDEDiagnosticIdToOptionMappingHelper.TryGetMappedFadingOption(diagnosticData.Id, out var fadingOption)) - { - Contract.ThrowIfNull(diagnosticData.Language, $"diagnostic {diagnosticData.Id} is missing a language"); - return GlobalOptions.GetOption(fadingOption, diagnosticData.Language); - } + return result.ToArray(); + } - return true; + private bool SupportsFadingOption(DiagnosticData diagnosticData) + { + if (IDEDiagnosticIdToOptionMappingHelper.TryGetMappedFadingOption(diagnosticData.Id, out var fadingOption)) + { + Contract.ThrowIfNull(diagnosticData.Language, $"diagnostic {diagnosticData.Id} is missing a language"); + return GlobalOptions.GetOption(fadingOption, diagnosticData.Language); } + + return true; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index d39af98d94d7b..7e3d6e4619791 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -3,19 +3,13 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.TaskList; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -25,6 +19,7 @@ internal abstract class AbstractWorkspacePullDiagnosticsHandler /// Flag that represents whether the LSP view of the world has changed. @@ -38,9 +33,11 @@ protected AbstractWorkspacePullDiagnosticsHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService diagnosticAnalyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) : base(diagnosticAnalyzerService, diagnosticRefresher, globalOptions) { + DiagnosticSourceManager = diagnosticSourceManager; _workspaceManager = workspaceManager; _workspaceRegistrationService = registrationService; @@ -54,25 +51,17 @@ public void Dispose() _workspaceRegistrationService.LspSolutionChanged -= OnLspSolutionChanged; } - protected override async ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { - // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace - // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for - // document-diagnostics instead. if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) - return []; - - var category = GetDiagnosticCategory(diagnosticsParams); - - if (category == PullDiagnosticCategories.Task) - return GetTaskListDiagnosticSources(context, GlobalOptions); - - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - if (category == null || category == PullDiagnosticCategories.WorkspaceDocumentsAndProject) - return await GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken).ConfigureAwait(false); + { + // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace + // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for + // document-diagnostics instead. + return new([]); + } - // if it's a category we don't recognize, return nothing. - return []; + return DiagnosticSourceManager.CreateWorkspaceDiagnosticSourcesAsync(context, requestDiagnosticCategory, cancellationToken); } private void OnLspSolutionChanged(object? sender, WorkspaceChangeEventArgs e) @@ -106,145 +95,6 @@ protected override async Task WaitForChangesAsync(RequestContext context, Cancel return; } - private static ImmutableArray GetTaskListDiagnosticSources( - RequestContext context, IGlobalOptionService globalOptions) - { - Contract.ThrowIfNull(context.Solution); - - // Only compute task list items for closed files if the option is on for it. - var taskListEnabled = globalOptions.GetTaskListOptions().ComputeForClosedFiles; - if (!taskListEnabled) - return []; - - using var _ = ArrayBuilder.GetInstance(out var result); - - foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) - { - foreach (var document in project.Documents) - { - if (!ShouldSkipDocument(context, document)) - result.Add(new TaskListDiagnosticSource(document, globalOptions)); - } - } - - return result.ToImmutable(); - } - - public static async ValueTask> GetDiagnosticSourcesAsync( - RequestContext context, IGlobalOptionService globalOptions, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(context.Solution); - - using var _ = ArrayBuilder.GetInstance(out var result); - - var solution = context.Solution; - var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; - var codeAnalysisService = solution.Workspace.Services.GetRequiredService(); - - foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) - await AddDocumentsAndProjectAsync(project, cancellationToken).ConfigureAwait(false); - - return result.ToImmutable(); - - async Task AddDocumentsAndProjectAsync(Project project, CancellationToken cancellationToken) - { - // There are two potential sources for reporting workspace diagnostics: - // - // 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest - // project snapshot and return up-to-date diagnostics computed from this analysis. - // - // 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly - // triggered code analysis execution on either the current or a prior project snapshot, we return - // diagnostics from this execution. These diagnostics may be stale with respect to the current - // project snapshot, but they match user's intent of not enabling continuous background analysis - // for always having up-to-date workspace diagnostics, but instead computing them explicitly on - // specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. - // - // If full solution analysis is disabled AND code analysis was never executed for the given project, - // we have no workspace diagnostics to report and bail out. - var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); - if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) - return; - - var documents = ImmutableArray.Empty.AddRange(project.Documents).AddRange(project.AdditionalDocuments); - - // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. - if (enableDiagnosticsInSourceGeneratedFiles) - { - var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - documents = documents.AddRange(sourceGeneratedDocuments); - } - - Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled - ? ShouldIncludeAnalyzer : null; - foreach (var document in documents) - { - if (!ShouldSkipDocument(context, document)) - { - // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. - var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, shouldIncludeAnalyzer) - : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); - result.Add(documentDiagnosticSource); - } - } - - // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. - var projectDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, shouldIncludeAnalyzer) - : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); - result.Add(projectDiagnosticSource); - - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) - { - return analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; - } - } - } - - private static IEnumerable GetProjectsInPriorityOrder( - Solution solution, ImmutableArray supportedLanguages) - { - return GetProjectsInPriorityOrderWorker(solution) - .WhereNotNull() - .Distinct() - .Where(p => supportedLanguages.Contains(p.Language)); - - static IEnumerable GetProjectsInPriorityOrderWorker(Solution solution) - { - var documentTrackingService = solution.Services.GetRequiredService(); - - // Collect all the documents from the solution in the order we'd like to get diagnostics for. This will - // prioritize the files from currently active projects, but then also include all other docs in all projects - // (depending on current FSA settings). - - var activeDocument = documentTrackingService.GetActiveDocument(solution); - var visibleDocuments = documentTrackingService.GetVisibleDocuments(solution); - - yield return activeDocument?.Project; - foreach (var doc in visibleDocuments) - yield return doc.Project; - - foreach (var project in solution.Projects) - yield return project; - } - } - - private static bool ShouldSkipDocument(RequestContext context, TextDocument document) - { - // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). - // Each handler treats those as separate worlds that they are responsible for. - if (context.IsTracking(document.GetURI())) - { - context.TraceInformation($"Skipping tracked document: {document.GetURI()}"); - return true; - } - - // Do not attempt to get workspace diagnostics for Razor files, Razor will directly ask us for document diagnostics - // for any razor file they are interested in. - return document.IsRazorDocument(); - } - internal abstract TestAccessor GetTestAccessor(); internal readonly struct TestAccessor(AbstractWorkspacePullDiagnosticsHandler handler) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs new file mode 100644 index 0000000000000..2af9fa8710706 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs @@ -0,0 +1,164 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Export(typeof(IDiagnosticSourceManager)), Shared] +internal sealed class DiagnosticSourceManager : IDiagnosticSourceManager +{ + /// + /// Document level providers ordered by name. + /// + private readonly ImmutableDictionary _nameToDocumentProviderMap; + + /// + /// Workspace level providers ordered by name. + /// + private readonly ImmutableDictionary _nameToWorkspaceProviderMap; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DiagnosticSourceManager([ImportMany] IEnumerable sourceProviders) + { + _nameToDocumentProviderMap = sourceProviders + .Where(p => p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + + _nameToWorkspaceProviderMap = sourceProviders + .Where(p => !p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + } + + public ImmutableArray GetDocumentSourceProviderNames(ClientCapabilities clientCapabilities) + => _nameToDocumentProviderMap.Where(kvp => kvp.Value.IsEnabled(clientCapabilities)).SelectAsArray(kvp => kvp.Key); + + public ImmutableArray GetWorkspaceSourceProviderNames(ClientCapabilities clientCapabilities) + => _nameToWorkspaceProviderMap.Where(kvp => kvp.Value.IsEnabled(clientCapabilities)).SelectAsArray(kvp => kvp.Key); + + public ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken) + => CreateDiagnosticSourcesAsync(context, providerName, _nameToDocumentProviderMap, isDocument: true, cancellationToken); + + public ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken) + => CreateDiagnosticSourcesAsync(context, providerName, _nameToWorkspaceProviderMap, isDocument: false, cancellationToken); + + private static async ValueTask> CreateDiagnosticSourcesAsync( + RequestContext context, + string? providerName, + ImmutableDictionary nameToProviderMap, + bool isDocument, + CancellationToken cancellationToken) + { + if (providerName != null) + { + // VS does not distinguish between document and workspace sources. Thus it can request + // document diagnostics with workspace source name. We need to handle this case. + if (nameToProviderMap.TryGetValue(providerName, out var provider)) + { + Contract.ThrowIfFalse(provider.IsEnabled(context.GetRequiredClientCapabilities())); + return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + } + + return []; + } + else + { + // VS Code (and legacy VS ?) pass null sourceName when requesting all sources. + using var _ = ArrayBuilder.GetInstance(out var sourcesBuilder); + foreach (var (name, provider) in nameToProviderMap) + { + if (!provider.IsEnabled(context.GetRequiredClientCapabilities())) + { + continue; + } + + var namedSources = await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + sourcesBuilder.AddRange(namedSources); + } + + var sources = sourcesBuilder.ToImmutableAndClear(); + return AggregateSourcesIfNeeded(sources, isDocument); + } + } + + public static ImmutableArray AggregateSourcesIfNeeded(ImmutableArray sources, bool isDocument) + { + if (sources.Length <= 1) + { + return sources; + } + + if (isDocument) + { + // Group all document sources into a single source. + Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live"); + sources = [new AggregatedDocumentDiagnosticSource(sources)]; + } + else + { + // We ASSUME that all sources with the same ProjectOrDocumentID and IsLiveSource + // will have same value for GetDocumentIdentifier and GetProject(). Thus can be + // aggregated in a single source which will return same values. See + // AggregatedDocumentDiagnosticSource implementation for more details. + sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) + .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) + .ToImmutableArray(); + } + + return sources; + } + + /// + /// Aggregates multiple s into a single source. + /// + /// Sources to aggregate + /// + /// Aggregation is usually needed for clients like VS Code which supports single source per request. + /// + private sealed class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource + { + public static ImmutableArray AggregateIfNeeded(IEnumerable sources) + { + var result = sources.ToImmutableArray(); + if (result.Length > 1) + { + result = [new AggregatedDocumentDiagnosticSource(result)]; + } + + return result; + } + + public bool IsLiveSource() => true; + public Project GetProject() => sources[0].GetProject(); + public ProjectOrDocumentId GetId() => sources[0].GetId(); + public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier(); + public string ToDisplayString() => $"{this.GetType().Name}: count={sources.Length}"; + + public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var diagnostics); + foreach (var source in sources) + { + var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); + diagnostics.AddRange(namedDiagnostics); + } + + return diagnostics.ToImmutableAndClear(); + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..85fca7423bdd3 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( + IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind kind, string sourceName) + : IDiagnosticSourceProvider +{ + public bool IsDocument => true; + public string Name => sourceName; + + public bool IsEnabled(ClientCapabilities clientCapabilities) => true; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } document) + { + return new([new DocumentDiagnosticSource(diagnosticAnalyzerService, kind, document)]); + } + + return new([]); + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.CompilerSyntax, PullDiagnosticCategories.DocumentCompilerSyntax) + { + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( + diagnosticAnalyzerService, DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSemantic) + { + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.AnalyzerSyntax, PullDiagnosticCategories.DocumentAnalyzerSyntax) + { + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.AnalyzerSemantic, PullDiagnosticCategories.DocumentAnalyzerSemantic) + { + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs new file mode 100644 index 0000000000000..25fc19acaca03 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; + +/// +/// Provides centralized/singleton management of MEF based s. +/// Consumers - like diagnostic handlers - use it to get diagnostics from one or more providers. +/// +internal interface IDiagnosticSourceManager +{ + /// + /// Returns the names of document level s. + /// + ImmutableArray GetDocumentSourceProviderNames(ClientCapabilities clientCapabilities); + + /// + /// Returns the names of workspace level s. + /// + ImmutableArray GetWorkspaceSourceProviderNames(ClientCapabilities clientCapabilities); + + /// + /// Creates document diagnostic sources for the given . + /// + /// + /// Optional provider name. If then diagnostics from all providers are used. + /// A cancellation token that can be used to cancel the request processing. + ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken); + + /// + /// Creates workspace diagnostic sources for the given . + /// + /// + /// Optional provider name. If not specified then diagnostics from all providers are used. + /// A cancellation token that can be used to cancel the request processing. + ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken); +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..773f3a309c94e --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.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; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +/// +/// Provides diagnostic sources. +/// +internal interface IDiagnosticSourceProvider +{ + /// + /// if this provider is for documents, if it is for workspace. + /// + bool IsDocument { get; } + + /// + /// Provider's name. Each should have a unique name within scope. + /// + string Name { get; } + + bool IsEnabled(ClientCapabilities clientCapabilities); + + /// + /// Creates the diagnostic sources. + /// + /// + /// A cancellation token that can be used to cancel the request processing. + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); +} + diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs new file mode 100644 index 0000000000000..8956840dbbfa2 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.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.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.Host; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal static class WorkspaceDiagnosticSourceHelpers +{ + public static IEnumerable GetProjectsInPriorityOrder(Solution solution, ImmutableArray supportedLanguages) + { + return GetProjectsInPriorityOrderWorker(solution) + .WhereNotNull() + .Distinct() + .Where(p => supportedLanguages.Contains(p.Language)); + + static IEnumerable GetProjectsInPriorityOrderWorker(Solution solution) + { + var documentTrackingService = solution.Services.GetRequiredService(); + + // Collect all the documents from the solution in the order we'd like to get diagnostics for. This will + // prioritize the files from currently active projects, but then also include all other docs in all projects + // (depending on current FSA settings). + + var activeDocument = documentTrackingService.GetActiveDocument(solution); + var visibleDocuments = documentTrackingService.GetVisibleDocuments(solution); + + yield return activeDocument?.Project; + foreach (var doc in visibleDocuments) + yield return doc.Project; + + foreach (var project in solution.Projects) + yield return project; + } + } + + public static bool ShouldSkipDocument(RequestContext context, TextDocument document) + { + // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). + // Each handler treats those as separate worlds that they are responsible for. + if (context.IsTracking(document.GetURI())) + { + context.TraceInformation($"Skipping tracked document: {document.GetURI()}"); + return true; + } + + // Do not attempt to get workspace diagnostics for Razor files, Razor will directly ask us for document diagnostics + // for any razor file they are interested in. + return document.IsRazorDocument(); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..58cca636c91f5 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, + [Import] IGlobalOptionService globalOptions) + : IDiagnosticSourceProvider +{ + public bool IsDocument => false; + public string Name => PullDiagnosticCategories.WorkspaceDocumentsAndProject; + + public bool IsEnabled(ClientCapabilities clientCapabilities) => true; + + /// + /// There are three potential sources for reporting workspace diagnostics: + /// + /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest + /// project snapshot and return up-to-date diagnostics computed from this analysis. + /// + /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly + /// triggered code analysis execution on either the current or a prior project snapshot, we return + /// diagnostics from this execution. These diagnostics may be stale with respect to the current + /// project snapshot, but they match user's intent of not enabling continuous background analysis + /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on + /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. + /// + /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. + /// + /// If full solution analysis is disabled AND code analysis was never executed for the given project, + /// we have no workspace diagnostics to report and bail out. + /// + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(context.Solution); + + using var _ = ArrayBuilder.GetInstance(out var result); + + var solution = context.Solution; + var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; + var codeAnalysisService = solution.Services.GetRequiredService(); + + foreach (var project in WorkspaceDiagnosticSourceHelpers.GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) + await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + + return result.ToImmutableAndClear(); + + async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); + if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) + return; + + Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled + ? ShouldIncludeAnalyzer : null; + + AddDocumentSources(project.Documents); + AddDocumentSources(project.AdditionalDocuments); + + // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. + if (enableDiagnosticsInSourceGeneratedFiles) + { + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + AddDocumentSources(sourceGeneratedDocuments); + } + + // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. + AddProjectSource(); + + return; + + void AddDocumentSources(IEnumerable documents) + { + foreach (var document in documents) + { + if (!WorkspaceDiagnosticSourceHelpers.ShouldSkipDocument(context, document)) + { + // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. + var documentDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); + result.Add(documentDiagnosticSource); + } + } + } + + void AddProjectSource() + { + var projectDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); + result.Add(projectDiagnosticSource); + } + + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) + => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs index 0de186f4e9ac1..f88810864a7bb 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs @@ -18,7 +18,7 @@ internal abstract class AbstractDocumentDiagnosticSource(TDocument do public abstract bool IsLiveSource(); public abstract Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); + RequestContext context, CancellationToken cancellationToken); public ProjectOrDocumentId GetId() => new(Document.Id); public Project GetProject() => Document.Project; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs index d34ef1d906826..ac49b0a9da7c6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs @@ -16,14 +16,14 @@ internal abstract class AbstractProjectDiagnosticSource(Project project) { protected Project Project => project; - public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(project, shouldIncludeAnalyzer); + public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(project, diagnosticAnalyzerService, shouldIncludeAnalyzer); public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(project, codeAnalysisService); public abstract bool IsLiveSource(); - public abstract Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); + public abstract Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken); public ProjectOrDocumentId GetId() => new(Project.Id); public Project GetProject() => Project; @@ -33,7 +33,8 @@ public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(P : null; public string ToDisplayString() => Project.Name; - private sealed class FullSolutionAnalysisDiagnosticSource(Project project, Func? shouldIncludeAnalyzer) : AbstractProjectDiagnosticSource(project) + private sealed class FullSolutionAnalysisDiagnosticSource(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource(project) { /// /// This is a normal project source that represents live/fresh diagnostics that should supersede everything else. @@ -42,7 +43,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { @@ -55,7 +55,8 @@ public override async Task> GetDiagnosticsAsync( } } - private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) : AbstractProjectDiagnosticSource(project) + private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) + : AbstractProjectDiagnosticSource(project) { /// /// This source provides the results of the *last* explicitly kicked off "run code analysis" command from the @@ -66,7 +67,6 @@ public override bool IsLiveSource() => false; public override Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 4cc153fcc9739..6777d956c2c88 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -3,24 +3,36 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDocumentDiagnosticSource(TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(document, shouldIncludeAnalyzer); + public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(document, diagnosticAnalyzerService, shouldIncludeAnalyzer); public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDiagnostics(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(document, codeAnalysisService); - private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { + /// + /// Cached mapping between a project instance and all the diagnostics computed for it. This is used so that + /// once we compute the diagnostics once for a particular project, we don't need to recompute them again as we + /// walk every document within it. + /// + private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); + /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. /// @@ -28,7 +40,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { @@ -40,14 +51,39 @@ public override async Task> GetDiagnosticsAsync( } else { - // We call GetDiagnosticsForIdsAsync as we want to ensure we get the full set of diagnostics for this document - // including those reported as a compilation end diagnostic. These are not included in document pull (uses GetDiagnosticsForSpan) due to cost. - // However we can include them as a part of workspace pull when FSA is on. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - Document.Project.Solution, Document.Project.Id, Document.Id, - diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, - includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); - return documentDiagnostics; + var projectDiagnostics = await GetProjectDiagnosticsAsync(diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + return projectDiagnostics.WhereAsArray(d => d.DocumentId == Document.Id); + } + } + + private async ValueTask> GetProjectDiagnosticsAsync( + IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + if (!s_projectToDiagnostics.TryGetValue(Document.Project, out var lazyDiagnostics)) + { + // Extracted into local to prevent captures. + lazyDiagnostics = GetLazyDiagnostics(); + } + + var result = await lazyDiagnostics.GetValueAsync(cancellationToken).ConfigureAwait(false); + return result[Document.Id].ToImmutableArray(); + + AsyncLazy> GetLazyDiagnostics() + { + return s_projectToDiagnostics.GetValue( + Document.Project, + _ => AsyncLazy.Create( + async cancellationToken => + { + var allDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + Document.Project.Solution, Document.Project.Id, documentId: null, + diagnosticIds: null, shouldIncludeAnalyzer, + // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. + static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], + includeSuppressedDiagnostics: false, + includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); + return allDiagnostics.Where(d => d.DocumentId != null).ToLookup(d => d.DocumentId!); + })); } } } @@ -64,7 +100,6 @@ public override bool IsLiveSource() => false; public override Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 637fa491c621f..e9a5ad49650bf 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, TextDocument document) +internal sealed class DocumentDiagnosticSource(IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { public DiagnosticKind DiagnosticKind { get; } = diagnosticKind; @@ -22,19 +22,20 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + RequestContext context, CancellationToken cancellationToken) { // We call GetDiagnosticsForSpanAsync here instead of GetDiagnosticsForIdsAsync as it has faster perf // characteristics. GetDiagnosticsForIdsAsync runs analyzers against the entire compilation whereas // GetDiagnosticsForSpanAsync will only run analyzers against the request document. // Also ensure we pass in "includeSuppressedDiagnostics = true" for unnecessary suppressions to be reported. var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( - Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); // Add cached Copilot diagnostics when computing analyzer semantic diagnostics. + // TODO: move to a separate diagnostic source. https://github.com/dotnet/roslyn/issues/72896 if (DiagnosticKind == DiagnosticKind.AnalyzerSemantic) { - var copilotDiagnostics = await Document.GetCachedCopilotDiagnosticsAsync(cancellationToken).ConfigureAwait(false); + var copilotDiagnostics = await Document.GetCachedCopilotDiagnosticsAsync(span: null, cancellationToken).ConfigureAwait(false); allSpanDiagnostics = allSpanDiagnostics.AddRange(copilotDiagnostics); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs index 34d110e4adb4c..e8cb70bc1de4c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -33,7 +32,6 @@ internal interface IDiagnosticSource bool IsLiveSource(); Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs index 8ffd2d9b75fb9..d370f15240a60 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class NonLocalDocumentDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) +internal sealed class NonLocalDocumentDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) : AbstractDocumentDiagnosticSource(document) { private readonly Func? _shouldIncludeAnalyzer = shouldIncludeAnalyzer; @@ -19,7 +19,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs index df8a3491ccdcc..fe6ebc182c6f9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs @@ -33,7 +33,7 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + RequestContext context, CancellationToken cancellationToken) { var service = this.Document.GetLanguageService(); if (service == null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index e9559f60a0123..16ee2f87d2bf2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -3,157 +3,72 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Method(VSInternalMethods.DocumentPullDiagnosticName)] +internal partial class DocumentPullDiagnosticHandler + : AbstractDocumentPullDiagnosticHandler { - [Method(VSInternalMethods.DocumentPullDiagnosticName)] - internal partial class DocumentPullDiagnosticHandler - : AbstractDocumentPullDiagnosticHandler + public DocumentPullDiagnosticHandler( + IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) { - public DocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, globalOptions) - { - } - - protected override string? GetDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.QueryingDiagnosticKind?.Value; - - public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.TextDocument; - - protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - => [ - new VSInternalDiagnosticReport - { - Diagnostics = diagnostics, - ResultId = resultId, - Identifier = DocumentDiagnosticIdentifier, - // Mark these diagnostics as superseding any diagnostics for the same document from the - // WorkspacePullDiagnosticHandler. We are always getting completely accurate and up to date diagnostic - // values for a particular file, so our results should always be preferred over the workspace-pull - // values which are cached and may be out of date. - Supersedes = WorkspaceDiagnosticIdentifier, - } - ]; + } - protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); + protected override string? GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; - protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) - { - report = CreateReport(identifier, diagnostics: null, resultId); - return true; - } + public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.TextDocument; - protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) - { - if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) + protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) + => [ + new VSInternalDiagnosticReport { - return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); + Diagnostics = diagnostics, + ResultId = resultId, + Identifier = DocumentDiagnosticIdentifier, + // Mark these diagnostics as superseding any diagnostics for the same document from the + // WorkspacePullDiagnosticHandler. We are always getting completely accurate and up to date diagnostic + // values for a particular file, so our results should always be preferred over the workspace-pull + // values which are cached and may be out of date. + Supersedes = WorkspaceDiagnosticIdentifier, } + ]; - // The client didn't provide us with a previous result to look for, so we can't lookup anything. - return null; - } + protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) - => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); - - protected override ValueTask> GetOrderedDiagnosticSourcesAsync( - VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) - { - var category = diagnosticsParams.QueryingDiagnosticKind?.Value; - - if (category == PullDiagnosticCategories.Task) - return new(GetDiagnosticSources(diagnosticKind: default, nonLocalDocumentDiagnostics: false, taskList: true, context, GlobalOptions)); - - var diagnosticKind = category switch - { - PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, - PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, - PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, - PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - null => DiagnosticKind.All, - // if it's a category we don't recognize, return nothing. - _ => (DiagnosticKind?)null, - }; - - if (diagnosticKind is null) - return new([]); - - return new(GetDiagnosticSources(diagnosticKind.Value, nonLocalDocumentDiagnostics: false, taskList: false, context, GlobalOptions)); - } + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) + { + report = CreateReport(identifier, diagnostics: null, resultId); + return true; + } - protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) + protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) + { + if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) { - return progress.GetFlattenedValues(); + return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); } - internal static ImmutableArray GetDiagnosticSources( - DiagnosticKind diagnosticKind, bool nonLocalDocumentDiagnostics, bool taskList, RequestContext context, IGlobalOptionService globalOptions) - { - // For the single document case, that is the only doc we want to process. - // - // Note: context.Document may be null in the case where the client is asking about a document that we have - // since removed from the workspace. In this case, we don't really have anything to process. - // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. - // - // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each - // handler treats those as separate worlds that they are responsible for. - var textDocument = context.TextDocument; - if (textDocument is null) - { - context.TraceInformation("Ignoring diagnostics request because no text document was provided"); - return []; - } - - var document = textDocument as Document; - if (taskList && document is null) - { - context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); - return []; - } - - if (!context.IsTracking(textDocument.GetURI())) - { - context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); - return []; - } - - if (nonLocalDocumentDiagnostics) - return GetNonLocalDiagnosticSources(); - - return taskList - ? [new TaskListDiagnosticSource(document!, globalOptions)] - : [new DocumentDiagnosticSource(diagnosticKind, textDocument)]; - - ImmutableArray GetNonLocalDiagnosticSources() - { - Debug.Assert(!taskList); - - // This code path is currently only invoked from the public LSP handler, which always uses 'DiagnosticKind.All' - Debug.Assert(diagnosticKind == DiagnosticKind.All); - - // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) - return []; + // The client didn't provide us with a previous result to look for, so we can't lookup anything. + return null; + } - return [new NonLocalDocumentDiagnosticSource(textDocument, ShouldIncludeAnalyzer)]; + protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) + => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); - // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); - } - } + protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) + { + return progress.GetFlattenedValues(); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs index 73300e9cc99dd..07a6cfcaabc17 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs @@ -6,30 +6,33 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics -{ - [ExportCSharpVisualBasicLspServiceFactory(typeof(DocumentPullDiagnosticHandler)), Shared] - internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory - { - private readonly IDiagnosticAnalyzerService _analyzerService; - private readonly IDiagnosticsRefresher _diagnosticsRefresher; - private readonly IGlobalOptionService _globalOptions; +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DocumentPullDiagnosticHandlerFactory( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) - { - _analyzerService = analyzerService; - _diagnosticsRefresher = diagnosticsRefresher; - _globalOptions = globalOptions; - } +[ExportCSharpVisualBasicLspServiceFactory(typeof(DocumentPullDiagnosticHandler)), Shared] +internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory +{ + private readonly IDiagnosticAnalyzerService _analyzerService; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; + private readonly IDiagnosticsRefresher _diagnosticsRefresher; + private readonly IGlobalOptionService _globalOptions; - public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) - => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticsRefresher, _globalOptions); + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DocumentPullDiagnosticHandlerFactory( + IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticsRefresher, + IGlobalOptionService globalOptions) + { + _analyzerService = analyzerService; + _diagnosticSourceManager = diagnosticSourceManager; + _diagnosticsRefresher = diagnosticsRefresher; + _globalOptions = globalOptions; } + + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticSourceManager, _diagnosticsRefresher, _globalOptions); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..5ac98fefc7389 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( + [Import] IGlobalOptionService globalOptions, + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : IDiagnosticSourceProvider +{ + public const string NonLocal = nameof(NonLocal); + public bool IsDocument => true; + public string Name => NonLocal; + + public bool IsEnabled(ClientCapabilities clientCapabilities) => true; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. + if (context.GetTrackedDocument() is { } textDocument && + globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) == BackgroundAnalysisScope.FullSolution) + { + // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. + return new([new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, a => !a.IsCompilerAnalyzer())]); + } + + return new([]); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs index e369e4180ed43..3ae460c780e84 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs @@ -5,10 +5,9 @@ using System; using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -19,6 +18,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFactory { private readonly IDiagnosticAnalyzerService _analyzerService; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly IDiagnosticsRefresher _diagnosticRefresher; private readonly IGlobalOptionService _globalOptions; @@ -26,10 +26,12 @@ internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFa [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public PublicDocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) { _analyzerService = analyzerService; + _diagnosticSourceManager = diagnosticSourceManager; _diagnosticRefresher = diagnosticRefresher; _globalOptions = globalOptions; } @@ -37,6 +39,6 @@ public PublicDocumentPullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var clientLanguageServerManager = lspServices.GetRequiredService(); - return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticRefresher, _globalOptions); + return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticSourceManager, _diagnosticRefresher, _globalOptions); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 3a22a8a8ce483..426148bed2f11 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -2,52 +2,42 @@ // The .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.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; -using DocumentDiagnosticReport = SumType; - // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed // by n DocumentDiagnosticPartialResult literals. -// See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentDiagnosticParams using DocumentDiagnosticPartialReport = SumType; +using DocumentDiagnosticReport = SumType; [Method(Methods.TextDocumentDiagnosticName)] internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler { - private readonly string _nonLocalDiagnosticsSourceRegistrationId; private readonly IClientLanguageServerManager _clientLanguageServerManager; public PublicDocumentPullDiagnosticsHandler( IClientLanguageServerManager clientLanguageServerManager, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticsRefresher, globalOptions) + : base(analyzerService, diagnosticsRefresher, diagnosticSourceManager, globalOptions) { - _nonLocalDiagnosticsSourceRegistrationId = Guid.NewGuid().ToString(); _clientLanguageServerManager = clientLanguageServerManager; } - /// - /// Public API doesn't support categories (yet). - /// - protected override string? GetDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) - => null; - - public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; + protected override string? GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) + => diagnosticsParams.Identifier; - protected override string? GetDiagnosticSourceIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; + public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) + => diagnosticsParams.TextDocument; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); @@ -92,14 +82,6 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi return null; } - protected override ValueTask> GetOrderedDiagnosticSourcesAsync(DocumentDiagnosticParams diagnosticParams, RequestContext context, CancellationToken cancellationToken) - { - var nonLocalDocumentDiagnostics = diagnosticParams.Identifier == DocumentNonLocalDiagnosticIdentifier.ToString(); - - // Task list items are not reported through the public LSP diagnostic API. - return ValueTaskFactory.FromResult(DocumentPullDiagnosticHandler.GetDiagnosticSources(DiagnosticKind.All, nonLocalDocumentDiagnostics, taskList: false, context, GlobalOptions)); - } - protected override ImmutableArray? GetPreviousResults(DocumentDiagnosticParams diagnosticsParams) { if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index 940723c0e652a..539100d7a23b2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -2,61 +2,62 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; - -using DocumentDiagnosticReport = SumType; - // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed // by n DocumentDiagnosticPartialResult literals. // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic -using DocumentDiagnosticPartialReport = SumType; -internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler, IOnInitialized +internal sealed partial class PublicDocumentPullDiagnosticsHandler : IOnInitialized { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - // Dynamically register a non-local document diagnostic source if Full solution background analysis is enabled - // for analyzer execution. This diagnostic source reports diagnostics in open documents that are reported - // when analyzing other documents or at compilation end. - if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true && IsFsaEnabled()) + // Dynamically register for all relevant diagnostic sources. + if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true) { // TODO: Hookup an option changed handler for changes to BackgroundAnalysisScopeOption // to dynamically register/unregister the non-local document diagnostic source. + var documentSources = DiagnosticSourceManager.GetDocumentSourceProviderNames(clientCapabilities); + var workspaceSources = DiagnosticSourceManager.GetWorkspaceSourceProviderNames(clientCapabilities); + + // All diagnostic sources have to be registered under the document pull method name, + // See https://github.com/microsoft/language-server-protocol/issues/1723 + // + // Additionally if a source name is used by both document and workspace pull (e.g. enc) + // we don't want to send two registrations, instead we should send a single registration + // that also sets the workspace pull option. + // + // So we build up a unique set of source names and mark if each one is also a workspace source. + var allSources = documentSources + .AddRange(workspaceSources) + .ToSet() + .Select(name => (Name: name, IsWorkspaceSource: workspaceSources.Contains(name))); + + var registrations = allSources.Select(FromSourceName).ToArray(); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams() { - Registrations = - [ - new Registration - { - Id = _nonLocalDiagnosticsSourceRegistrationId, - Method = Methods.TextDocumentDiagnosticName, - RegisterOptions = new DiagnosticRegistrationOptions - { - Identifier = DocumentNonLocalDiagnosticIdentifier.ToString() - } - } - ] + Registrations = registrations }, cancellationToken).ConfigureAwait(false); } - bool IsFsaEnabled() + static Registration FromSourceName((string Name, bool IsWorkspaceSource) source) { - foreach (var language in context.SupportedLanguages) + return new() { - if (GlobalOptions.GetBackgroundAnalysisScope(language) == BackgroundAnalysisScope.FullSolution) - return true; - } - - return false; + Id = Guid.NewGuid().ToString(), + Method = Methods.TextDocumentDiagnosticName, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = source.Name, InterFileDependencies = true, WorkspaceDiagnostics = source.IsWorkspaceSource, WorkDoneProgress = source.IsWorkspaceSource } + }; } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs index 90970a521fe53..ea0814316fbfc 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -16,12 +17,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions); + return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 6c2beb5517041..71f78c36902a0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -19,21 +20,21 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using WorkspaceDiagnosticPartialReport = SumType; [Method(Methods.WorkspaceDiagnosticName)] -internal sealed class PublicWorkspacePullDiagnosticsHandler( - LspWorkspaceManager workspaceManager, - LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticRefresher, globalOptions), IDisposable +internal sealed partial class PublicWorkspacePullDiagnosticsHandler : AbstractWorkspacePullDiagnosticsHandler, IDisposable { + public PublicWorkspacePullDiagnosticsHandler( + LspWorkspaceManager workspaceManager, + LspWorkspaceRegistrationService registrationService, + IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : base(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticRefresher, globalOptions) + { + } - /// - /// Public API doesn't support categories (yet). - /// - protected override string? GetDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) - => null; + protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) + => diagnosticsParams.Identifier; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs index 5ba6497ed6e30..ca79789913f81 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs @@ -4,31 +4,35 @@ using Roslyn.LanguageServer.Protocol; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal static class PullDiagnosticCategories { - internal static class PullDiagnosticCategories - { - /// - /// Task list items. Can be for Document or Workspace pull requests. - /// - public static readonly string Task = VSInternalDiagnosticKind.Task.Value; - - // Workspace categories - - /// - /// Diagnostics for workspace documents and project. We don't support fine-grained diagnostics requests for these (yet). - /// - public const string WorkspaceDocumentsAndProject = nameof(WorkspaceDocumentsAndProject); - - // Fine-grained document pull categories to allow diagnostics to more quickly reach the user. - - // VSLanguageServerClient's RemoteDocumentDiagnosticBroker uses this exact string to determine - // when syntax errors are being provided via pull diagnostics. Alternatively when 17.9 preview 1 packages - // are consumable by Roslyn, this could be updated to reference VSInternalDiagnosticKind.Syntax.Value directly. - public const string DocumentCompilerSyntax = "syntax"; - - public const string DocumentCompilerSemantic = nameof(DocumentCompilerSemantic); - public const string DocumentAnalyzerSyntax = nameof(DocumentAnalyzerSyntax); - public const string DocumentAnalyzerSemantic = nameof(DocumentAnalyzerSemantic); - } + /// + /// Task list items. Can be for Document or Workspace pull requests. + /// + public static readonly string Task = VSInternalDiagnosticKind.Task.Value; + + /// + /// Edit and Continue diagnostics. Can be for Document or Workspace pull requests. + /// + public static readonly string EditAndContinue = VSInternalDiagnosticKind.EditAndContiue.Value; + + // Workspace categories + + /// + /// Diagnostics for workspace documents and project. We don't support fine-grained diagnostics requests for these (yet). + /// + public const string WorkspaceDocumentsAndProject = nameof(WorkspaceDocumentsAndProject); + + // Fine-grained document pull categories to allow diagnostics to more quickly reach the user. + + // VSLanguageServerClient's RemoteDocumentDiagnosticBroker uses this exact string to determine + // when syntax errors are being provided via pull diagnostics. Alternatively when 17.9 preview 1 packages + // are consumable by Roslyn, this could be updated to reference VSInternalDiagnosticKind.Syntax.Value directly. + public const string DocumentCompilerSyntax = "syntax"; + + public const string DocumentCompilerSemantic = nameof(DocumentCompilerSemantic); + public const string DocumentAnalyzerSyntax = nameof(DocumentAnalyzerSyntax); + public const string DocumentAnalyzerSemantic = nameof(DocumentAnalyzerSemantic); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index bb8dc13086045..62f6e9b5b8332 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -6,64 +6,65 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics -{ - [Method(VSInternalMethods.WorkspacePullDiagnosticName)] - internal sealed partial class WorkspacePullDiagnosticHandler( - LspWorkspaceManager workspaceManager, - LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, - IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) - : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions) - { - protected override string? GetDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - => diagnosticsParams.QueryingDiagnosticKind?.Value; +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - => [ - new VSInternalWorkspaceDiagnosticReport - { - TextDocument = identifier, - Diagnostics = diagnostics, - ResultId = resultId, - // Mark these diagnostics as having come from us. They will be superseded by any diagnostics for the - // same file produced by the DocumentPullDiagnosticHandler. - Identifier = WorkspaceDiagnosticIdentifier, - } - ]; - - protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); +[Method(VSInternalMethods.WorkspacePullDiagnosticName)] +internal sealed partial class WorkspacePullDiagnosticHandler( + LspWorkspaceManager workspaceManager, + LspWorkspaceRegistrationService registrationService, + IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticsRefresher, + IGlobalOptionService globalOptions) + : AbstractWorkspacePullDiagnosticsHandler( + workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) +{ + protected override string? GetRequestDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; - protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) + protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) + => [ + new VSInternalWorkspaceDiagnosticReport { - // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of - // these and we can save a lot of memory not serializing/deserializing all of this. - report = null; - return false; + TextDocument = identifier, + Diagnostics = diagnostics, + ResultId = resultId, + // Mark these diagnostics as having come from us. They will be superseded by any diagnostics for the + // same file produced by the DocumentPullDiagnosticHandler. + Identifier = WorkspaceDiagnosticIdentifier, } + ]; - protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); + protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) - { - // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics - // produced by document diagnostics. - return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); - } + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) + { + // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of + // these and we can save a lot of memory not serializing/deserializing all of this. + report = null; + return false; + } - protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) - { - return progress.GetFlattenedValues(); - } + protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); - internal override TestAccessor GetTestAccessor() => new(this); + protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) + { + // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics + // produced by document diagnostics. + return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); } + + protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) + { + return progress.GetFlattenedValues(); + } + + internal override TestAccessor GetTestAccessor() => new(this); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs index 1e53ae4d721bd..7c13f4d46b74a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs @@ -6,23 +6,24 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportCSharpVisualBasicLspServiceFactory(typeof(WorkspacePullDiagnosticHandler)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class WorkspacePullDiagnosticHandlerFactory( + LspWorkspaceRegistrationService registrationService, + IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticsRefresher, + IGlobalOptionService globalOptions) : ILspServiceFactory { - [ExportCSharpVisualBasicLspServiceFactory(typeof(WorkspacePullDiagnosticHandler)), Shared] - [method: ImportingConstructor] - [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal class WorkspacePullDiagnosticHandlerFactory( - LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, - IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : ILspServiceFactory + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { - public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) - { - var workspaceManager = lspServices.GetRequiredService(); - return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions); - } + var workspaceManager = lspServices.GetRequiredService(); + return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..5f66ca41cdddb --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.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.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DocumentEditAndContinueDiagnosticSourceProvider() : IDiagnosticSourceProvider +{ + public bool IsDocument => true; + public string Name => PullDiagnosticCategories.EditAndContinue; + + public bool IsEnabled(ClientCapabilities capabilities) => true; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } document) + { + return new([EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document)]); + } + + return new([]); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..af92108b86cae --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : IDiagnosticSourceProvider +{ + public bool IsDocument => false; + public string Name => PullDiagnosticCategories.EditAndContinue; + + public bool IsEnabled(ClientCapabilities capabilities) => true; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(context.Solution); + return EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs b/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs index d91d1c14d3732..806e79597b426 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs @@ -42,7 +42,7 @@ internal abstract class AbstractFormatDocumentHandlerBase(); edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text))); diff --git a/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs index 176c3f17b0739..806d070ff0463 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs @@ -53,14 +53,14 @@ public DocumentHighlightsHandler(IHighlightingService highlightingService, IGlob var keywordHighlights = await GetKeywordHighlightsAsync(document, text, position, cancellationToken).ConfigureAwait(false); if (keywordHighlights.Any()) { - return keywordHighlights.ToArray(); + return [.. keywordHighlights]; } // Not a keyword, check if it is a reference that needs highlighting. var referenceHighlights = await GetReferenceHighlightsAsync(document, text, position, cancellationToken).ConfigureAwait(false); if (referenceHighlights.Any()) { - return referenceHighlights.ToArray(); + return [.. referenceHighlights]; } // No keyword or references to highlight at this location. diff --git a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs index de557ed9f5240..8da7e981f854a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs @@ -50,7 +50,6 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(InlayHintParams request) var options = _optionsService.GetInlineHintsOptions(document.Project.Language); var hints = await inlineHintService.GetInlineHintsAsync(document, textSpan, options, displayAllOverride: false, cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(hints.Length, out var inlayHints); var syntaxVersion = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); var inlayHintCache = context.GetRequiredLspService(); @@ -58,6 +57,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(InlayHintParams request) // member we can re-use the inline hint. var resultId = inlayHintCache.UpdateCache(new InlayHintCache.InlayHintCacheEntry(hints, syntaxVersion)); + var inlayHints = new LSP.InlayHint[hints.Length]; for (var i = 0; i < hints.Length; i++) { var hint = hints[i]; @@ -88,10 +88,10 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(InlayHintParams request) Data = new InlayHintResolveData(resultId, i, request.TextDocument) }; - inlayHints.Add(inlayHint); + inlayHints[i] = inlayHint; } - return inlayHints.ToArray(); + return inlayHints; } /// diff --git a/src/Features/LanguageServer/Protocol/Handler/MapCode/MapCodeHandler.cs b/src/Features/LanguageServer/Protocol/Handler/MapCode/MapCodeHandler.cs index 7d8070437a6cb..3967261f2131d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/MapCode/MapCodeHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/MapCode/MapCodeHandler.cs @@ -134,7 +134,7 @@ public MapCodeHandler() } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs index a488aefea3f12..7ea64d0c5d474 100644 --- a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs @@ -166,7 +166,7 @@ public OnAutoInsertHandler( var indentedText = GetIndentedText(newSourceText, caretLine, desiredCaretLinePosition, options); // Get the overall text changes between the original text and the formatted + indented text. - textChanges = indentedText.GetTextChanges(sourceText).ToImmutableArray(); + textChanges = [.. indentedText.GetTextChanges(sourceText)]; newSourceText = indentedText; // If tabs were inserted the desired caret column can remain beyond the line text. diff --git a/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs b/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs index 1fd14087e596d..ee897994efe61 100644 --- a/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs @@ -63,7 +63,7 @@ public GetTextDocumentWithContextHandler() return Task.FromResult(new VSProjectContextList { - ProjectContexts = contexts.ToArray(), + ProjectContexts = [.. contexts], DefaultIndex = documentIds.IndexOf(d => d == currentContextDocumentId) }); } diff --git a/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs b/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs index 2338d5b383c31..9e57963e23ef3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs +++ b/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs @@ -259,7 +259,7 @@ public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem refere var docText = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var classifiedTextRuns = GetClassifiedTextRuns(_id, definitionId, documentSpan.Value, isWrittenTo, classifiedSpans, docText); - return new ClassifiedTextElement(classifiedTextRuns.ToArray()); + return new ClassifiedTextElement([.. classifiedTextRuns]); } // Certain definitions may not have a DocumentSpan, such as namespace and metadata definitions @@ -317,7 +317,7 @@ private static ClassifiedTextRun[] GetClassifiedTextRuns( private ValueTask ReportReferencesAsync(ImmutableSegmentedList> referencesToReport, CancellationToken cancellationToken) { // We can report outside of the lock here since _progress is thread-safe. - _progress.Report(referencesToReport.ToArray()); + _progress.Report([.. referencesToReport]); return ValueTaskFactory.CompletedTask; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs b/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs index b48b8de52a6e7..f0610290edbde 100644 --- a/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs +++ b/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs @@ -319,6 +319,29 @@ public SourceText GetTrackedDocumentSourceText(Uri documentUri) return _trackedDocuments[documentUri].Text; } + public TDocument? GetTrackedDocument() where TDocument : TextDocument + { + // Note: context.Document may be null in the case where the client is asking about a document that we have + // since removed from the workspace. In this case, we don't really have anything to process. + // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. + // + // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each + // handler treats those as separate worlds that they are responsible for. + if (TextDocument is not TDocument document) + { + TraceInformation($"Ignoring diagnostics request because no {typeof(TDocument).Name} was provided"); + return null; + } + + if (!IsTracking(document.GetURI())) + { + TraceWarning($"Ignoring diagnostics request for untracked document: {document.GetURI()}"); + return null; + } + + return document; + } + /// /// Allows a mutating request to close a document and stop it being tracked. /// Mutating requests are serialized by the execution queue in order to prevent concurrent access. diff --git a/src/Features/LanguageServer/Protocol/Handler/RequestTelemetryLogger.cs b/src/Features/LanguageServer/Protocol/Handler/RequestTelemetryLogger.cs index 8a24ab46cfa87..7341d04b9c5d9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/RequestTelemetryLogger.cs +++ b/src/Features/LanguageServer/Protocol/Handler/RequestTelemetryLogger.cs @@ -36,6 +36,8 @@ public RequestTelemetryLogger(string serverTypeName) _requestCounters = new(); _findDocumentResults = new(); _usedForkedSolutionCounter = new(); + + TelemetryLogging.Flushed += OnFlushed; } public void UpdateFindDocumentTelemetryData(bool success, string? workspaceKind) @@ -66,6 +68,7 @@ public void UpdateTelemetryData( m[TelemetryLogging.KeyValue] = queuedDuration.Milliseconds; m[TelemetryLogging.KeyMetricName] = "TimeInQueue"; m["server"] = _serverTypeName; + m["method"] = methodName; })); TelemetryLogging.LogAggregated(FunctionId.LSP_RequestDuration, KeyValueLogMessage.Create(m => @@ -91,6 +94,14 @@ public void Dispose() return; } + // Flush all telemetry logged through TelemetryLogging + TelemetryLogging.Flush(); + + TelemetryLogging.Flushed -= OnFlushed; + } + + private void OnFlushed(object? sender, EventArgs e) + { foreach (var kvp in _requestCounters) { TelemetryLogging.Log(FunctionId.LSP_RequestCounter, KeyValueLogMessage.Create(LogType.Trace, m => @@ -123,9 +134,6 @@ public void Dispose() } })); - // Flush all telemetry logged through TelemetryLogging - TelemetryLogging.Flush(); - _requestCounters.Clear(); } diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs index a3d07daa66148..d83ae66ec4f64 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs @@ -39,13 +39,11 @@ internal static async Task HandleRequestHelperAsync( var options = globalOptions.GetClassificationOptions(project.Language); var supportsVisualStudioExtensions = context.GetRequiredClientCapabilities().HasVisualStudioLspCapability(); - using var _ = ArrayBuilder.GetInstance(ranges.Length, out var spans); + var spans = new FixedSizeArrayBuilder(ranges.Length); foreach (var range in ranges) - { spans.Add(ProtocolConversions.RangeToLinePositionSpan(range)); - } - var tokensData = await HandleRequestHelperAsync(contextDocument, spans.ToImmutable(), supportsVisualStudioExtensions, options, cancellationToken).ConfigureAwait(false); + var tokensData = await HandleRequestHelperAsync(contextDocument, spans.MoveToImmutable(), supportsVisualStudioExtensions, options, cancellationToken).ConfigureAwait(false); // The above call to get semantic tokens may be inaccurate (because we use frozen partial semantics). Kick // off a request to ensure that the OOP side gets a fully up to compilation for this project. Once it does @@ -60,7 +58,7 @@ public static async Task HandleRequestHelperAsync(Document document, Immu // If the full compilation is not yet available, we'll try getting a partial one. It may contain inaccurate // results but will speed up how quickly we can respond to the client's request. document = document.WithFrozenPartialSemantics(cancellationToken); - options = options with { ForceFrozenPartialSemanticsForCrossProcessOperations = true }; + options = options with { FrozenPartialSemantics = true }; // The results from the range handler should not be cached since we don't want to cache // partial token results. In addition, a range request is only ever called with a whole @@ -100,13 +98,11 @@ public static async Task ComputeSemanticTokensDataAsync( } else { - using var _ = ArrayBuilder.GetInstance(spans.Length, out var textSpansBuilder); + var textSpansBuilder = new FixedSizeArrayBuilder(spans.Length); foreach (var span in spans) - { textSpansBuilder.Add(text.Lines.GetTextSpan(span)); - } - textSpans = textSpansBuilder.ToImmutable(); + textSpans = textSpansBuilder.MoveToImmutable(); } await GetClassifiedSpansForDocumentAsync( @@ -240,8 +236,6 @@ private static int[] ComputeTokens( bool supportsVisualStudioExtensions, IReadOnlyDictionary tokenTypesToIndex) { - using var _ = ArrayBuilder.GetInstance(classifiedSpans.Count, out var data); - // We keep track of the last line number and last start character since tokens are // reported relative to each other. var lastLineNumber = 0; @@ -249,6 +243,7 @@ private static int[] ComputeTokens( var tokenTypeMap = SemanticTokensSchema.GetSchema(supportsVisualStudioExtensions).TokenTypeMap; + using var _ = ArrayBuilder.GetInstance(5 * classifiedSpans.Count, out var data); for (var currentClassifiedSpanIndex = 0; currentClassifiedSpanIndex < classifiedSpans.Count; currentClassifiedSpanIndex++) { currentClassifiedSpanIndex = ComputeNextToken( @@ -257,7 +252,11 @@ private static int[] ComputeTokens( out var deltaLine, out var startCharacterDelta, out var tokenLength, out var tokenType, out var tokenModifiers); - data.AddRange(deltaLine, startCharacterDelta, tokenLength, tokenType, tokenModifiers); + data.Add(deltaLine); + data.Add(startCharacterDelta); + data.Add(tokenLength); + data.Add(tokenType); + data.Add(tokenModifiers); } return data.ToArray(); @@ -320,16 +319,21 @@ private static int ComputeNextToken( if (classificationType == ClassificationTypeNames.StaticSymbol) { // 4. Token modifiers - each set bit will be looked up in SemanticTokensLegend.tokenModifiers - modifierBits = TokenModifiers.Static; + modifierBits |= TokenModifiers.Static; } else if (classificationType == ClassificationTypeNames.ReassignedVariable) { // 5. Token modifiers - each set bit will be looked up in SemanticTokensLegend.tokenModifiers - modifierBits = TokenModifiers.ReassignedVariable; + modifierBits |= TokenModifiers.ReassignedVariable; + } + else if (classificationType == ClassificationTypeNames.ObsoleteSymbol) + { + // 6. Token modifiers - each set bit will be looked up in SemanticTokensLegend.tokenModifiers + modifierBits |= TokenModifiers.Deprecated; } else { - // 6. Token type - looked up in SemanticTokensLegend.tokenTypes (language server defined mapping + // 7. Token type - looked up in SemanticTokensLegend.tokenTypes (language server defined mapping // from integer to LSP token types). tokenTypeIndex = GetTokenTypeIndex(classificationType); } diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRefreshQueue.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRefreshQueue.cs index 4628b4a80a6eb..a512ea1d5857d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRefreshQueue.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRefreshQueue.cs @@ -68,10 +68,19 @@ public SemanticTokensRefreshQueue( } public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken _) + { + if (_capabilitiesProvider.GetCapabilities(clientCapabilities).SemanticTokensOptions is not null) + { + Initialize(clientCapabilities); + } + + return Task.CompletedTask; + } + + public void Initialize(ClientCapabilities clientCapabilities) { if (_semanticTokenRefreshQueue is null - && clientCapabilities.Workspace?.SemanticTokens?.RefreshSupport is true - && _capabilitiesProvider.GetCapabilities(clientCapabilities).SemanticTokensOptions is not null) + && clientCapabilities.Workspace?.SemanticTokens?.RefreshSupport is true) { // Only send a refresh notification to the client every 2s (if needed) in order to avoid sending too many // notifications at once. This ensures we batch up workspace notifications, but also means we send soon @@ -85,8 +94,6 @@ public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestCon _lspWorkspaceRegistrationService.LspSolutionChanged += OnLspSolutionChanged; } - - return Task.CompletedTask; } public async Task TryEnqueueRefreshComputationAsync(Project project, CancellationToken cancellationToken) diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs index 94cdb33c0955e..03c19b8338212 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs @@ -102,7 +102,7 @@ public SemanticTokensSchema(IReadOnlyDictionary tokenTypeMap) .Order() .ToImmutableArray(); - AllTokenTypes = SemanticTokenTypes.AllTypes.Concat(customTokenTypes).ToImmutableArray(); + AllTokenTypes = [.. SemanticTokenTypes.AllTypes, .. customTokenTypes]; var tokenTypeToIndex = new Dictionary(); @@ -130,7 +130,8 @@ public static SemanticTokensSchema LegacyTokensSchemaForLSIF [ // This must be in the same order as SemanticTokens.TokenModifiers, but skip the "None" item SemanticTokenModifiers.Static, - nameof(SemanticTokens.TokenModifiers.ReassignedVariable) + nameof(SemanticTokens.TokenModifiers.ReassignedVariable), + SemanticTokenModifiers.Deprecated, ]; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs index c93cf9c6c35ce..b5f820e1d18a7 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs @@ -16,5 +16,6 @@ internal enum TokenModifiers None = 0, Static = 1, ReassignedVariable = 2, + Deprecated = 4, } } diff --git a/src/Features/LanguageServer/Protocol/Handler/SpellCheck/WorkspaceSpellCheckHandler.cs b/src/Features/LanguageServer/Protocol/Handler/SpellCheck/WorkspaceSpellCheckHandler.cs index b8ce56eb1cd0b..ba22db3d056ec 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SpellCheck/WorkspaceSpellCheckHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SpellCheck/WorkspaceSpellCheckHandler.cs @@ -56,7 +56,7 @@ protected override ImmutableArray GetOrderedDocuments(RequestContext c // Ensure that we only process documents once. result.RemoveDuplicates(); - return result.ToImmutable(); + return result.ToImmutableAndClear(); void AddDocumentsFromProject(Project? project, ImmutableArray supportedLanguages) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs index b3b1092b37802..f0ff5255f831b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs @@ -21,32 +21,27 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// [ExportCSharpVisualBasicStatelessLspService(typeof(WorkspaceSymbolsHandler)), Shared] [Method(Methods.WorkspaceSymbolName)] - internal sealed class WorkspaceSymbolsHandler : ILspServiceRequestHandler + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class WorkspaceSymbolsHandler(IAsynchronousOperationListenerProvider listenerProvider) + : ILspServiceRequestHandler { - private static readonly IImmutableSet s_supportedKinds = - ImmutableHashSet.Create( - NavigateToItemKind.Class, - NavigateToItemKind.Constant, - NavigateToItemKind.Delegate, - NavigateToItemKind.Enum, - NavigateToItemKind.EnumItem, - NavigateToItemKind.Event, - NavigateToItemKind.Field, - NavigateToItemKind.Interface, - NavigateToItemKind.Method, - NavigateToItemKind.Module, - NavigateToItemKind.Property, - NavigateToItemKind.Structure); - - private readonly IAsynchronousOperationListener _asyncListener; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public WorkspaceSymbolsHandler( - IAsynchronousOperationListenerProvider listenerProvider) - { - _asyncListener = listenerProvider.GetListener(FeatureAttribute.NavigateTo); - } + private static readonly IImmutableSet s_supportedKinds = [ + NavigateToItemKind.Class, + NavigateToItemKind.Constant, + NavigateToItemKind.Delegate, + NavigateToItemKind.Enum, + NavigateToItemKind.EnumItem, + NavigateToItemKind.Event, + NavigateToItemKind.Field, + NavigateToItemKind.Interface, + NavigateToItemKind.Method, + NavigateToItemKind.Module, + NavigateToItemKind.Property, + NavigateToItemKind.Structure + ]; + + private readonly IAsynchronousOperationListener _asyncListener = listenerProvider.GetListener(FeatureAttribute.NavigateTo); public bool MutatesSolutionState => false; public bool RequiresLSPSolution => true; @@ -66,37 +61,35 @@ public WorkspaceSymbolsHandler( s_supportedKinds, cancellationToken); - await searcher.SearchAsync(searchCurrentDocument: false, cancellationToken).ConfigureAwait(false); + await searcher.SearchAsync(NavigateToSearchScope.Solution, cancellationToken).ConfigureAwait(false); return progress.GetFlattenedValues(); } - private class LSPNavigateToCallback : INavigateToSearchCallback + private sealed class LSPNavigateToCallback( + RequestContext context, + BufferedProgress progress) + : INavigateToSearchCallback { - private readonly RequestContext _context; - private readonly BufferedProgress _progress; - - public LSPNavigateToCallback( - RequestContext context, - BufferedProgress progress) + public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - _context = context; - _progress = progress; - } + Contract.ThrowIfNull(context.Solution); + var solution = context.Solution; - public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) - { - var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(project.Solution, cancellationToken).ConfigureAwait(false); + foreach (var result in results) + { + var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false); - var location = await ProtocolConversions.TextSpanToLocationAsync( - document, result.NavigableItem.SourceSpan, result.NavigableItem.IsStale, _context, cancellationToken).ConfigureAwait(false); - if (location == null) - return; + var location = await ProtocolConversions.TextSpanToLocationAsync( + document, result.NavigableItem.SourceSpan, result.NavigableItem.IsStale, context, cancellationToken).ConfigureAwait(false); + if (location == null) + return; - var service = project.Solution.Services.GetRequiredService(); - var symbolInfo = service.Create( - result.Name, result.AdditionalInformation, ProtocolConversions.NavigateToKindToSymbolKind(result.Kind), location, result.NavigableItem.Glyph); + var service = solution.Services.GetRequiredService(); + var symbolInfo = service.Create( + result.Name, result.AdditionalInformation, ProtocolConversions.NavigateToKindToSymbolKind(result.Kind), location, result.NavigableItem.Glyph); - _progress.Report(symbolInfo); + progress.Report(symbolInfo); + } } public void Done(bool isFullyLoaded) diff --git a/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..5e78b219c22f3 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Tasks; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) : IDiagnosticSourceProvider +{ + public bool IsDocument => true; + public string Name => PullDiagnosticCategories.Task; + + public bool IsEnabled(ClientCapabilities capabilities) => capabilities.HasVisualStudioLspCapability(); + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } document) + { + return new([new TaskListDiagnosticSource(document, globalOptions)]); + } + + return new([]); + } +} + diff --git a/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..2a273d85d398a --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.TaskList; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Tasks; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) : IDiagnosticSourceProvider +{ + public bool IsDocument => false; + public string Name => PullDiagnosticCategories.Task; + + public bool IsEnabled(ClientCapabilities capabilities) => capabilities.HasVisualStudioLspCapability(); + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(context.Solution); + + // Only compute task list items for closed files if the option is on for it. + if (globalOptions.GetTaskListOptions().ComputeForClosedFiles) + { + using var _ = ArrayBuilder.GetInstance(out var result); + foreach (var project in WorkspaceDiagnosticSourceHelpers.GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) + { + foreach (var document in project.Documents) + { + if (!WorkspaceDiagnosticSourceHelpers.ShouldSkipDocument(context, document)) + result.Add(new TaskListDiagnosticSource(document, globalOptions)); + } + } + + return new(result.ToImmutableAndClear()); + } + + return new([]); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/WorkspaceCommand/ExecuteWorkspaceCommandHandler.cs b/src/Features/LanguageServer/Protocol/Handler/WorkspaceCommand/ExecuteWorkspaceCommandHandler.cs index 761da70bf4244..04397fc1bf4d4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/WorkspaceCommand/ExecuteWorkspaceCommandHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/WorkspaceCommand/ExecuteWorkspaceCommandHandler.cs @@ -36,6 +36,7 @@ public ExecuteWorkspaceCommandHandler() var result = await requestExecutionQueue.ExecuteAsync( request, + LanguageServerConstants.DefaultLanguageName, requestMethod, lspServices, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/ILanguageServerFactory.cs b/src/Features/LanguageServer/Protocol/ILanguageServerFactory.cs index 243190649afe8..c2145c42bf68c 100644 --- a/src/Features/LanguageServer/Protocol/ILanguageServerFactory.cs +++ b/src/Features/LanguageServer/Protocol/ILanguageServerFactory.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CommonLanguageServerProtocol.Framework; +using Newtonsoft.Json; using StreamJsonRpc; namespace Microsoft.CodeAnalysis.LanguageServer @@ -13,6 +14,7 @@ internal interface ILanguageServerFactory { public AbstractLanguageServer Create( JsonRpc jsonRpc, + JsonSerializer jsonSerializer, ICapabilitiesProvider capabilitiesProvider, WellKnownLspServerKinds serverKind, AbstractLspLogger logger, diff --git a/src/Features/LanguageServer/Protocol/LanguageInfoProvider.cs b/src/Features/LanguageServer/Protocol/LanguageInfoProvider.cs index ffa6c8563a4e2..755f9b706fddb 100644 --- a/src/Features/LanguageServer/Protocol/LanguageInfoProvider.cs +++ b/src/Features/LanguageServer/Protocol/LanguageInfoProvider.cs @@ -11,11 +11,14 @@ namespace Microsoft.CodeAnalysis.LanguageServer { internal class LanguageInfoProvider : ILanguageInfoProvider { + // Constant so that Razor can use it (exposed via EA) otherwise their endpoints won't get hit + public const string RazorLanguageName = "Razor"; + private static readonly LanguageInformation s_csharpLanguageInformation = new(LanguageNames.CSharp, ".csx"); private static readonly LanguageInformation s_fsharpLanguageInformation = new(LanguageNames.FSharp, ".fsx"); private static readonly LanguageInformation s_vbLanguageInformation = new(LanguageNames.VisualBasic, ".vbx"); private static readonly LanguageInformation s_typeScriptLanguageInformation = new LanguageInformation(InternalLanguageNames.TypeScript, string.Empty); - private static readonly LanguageInformation s_razorLanguageInformation = new("Razor", string.Empty); + private static readonly LanguageInformation s_razorLanguageInformation = new(RazorLanguageName, string.Empty); private static readonly LanguageInformation s_xamlLanguageInformation = new("XAML", string.Empty); private static readonly Dictionary s_extensionToLanguageInformation = new() diff --git a/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs b/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs index f604d0049d19f..099a38a5f3889 100644 --- a/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs +++ b/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs @@ -153,7 +153,7 @@ public void Dispose() ImmutableArray disposableServices; lock (_gate) { - disposableServices = _servicesToDispose.ToImmutableArray(); + disposableServices = [.. _servicesToDispose]; _servicesToDispose.Clear(); } diff --git a/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj b/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj index df5d1f713bcd7..6db127bd2f722 100644 --- a/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj +++ b/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj @@ -81,6 +81,7 @@ + diff --git a/src/Features/LanguageServer/Protocol/Protocol/CompletionItem.cs b/src/Features/LanguageServer/Protocol/Protocol/CompletionItem.cs index acd63d0657304..f416ae767d3d1 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/CompletionItem.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/CompletionItem.cs @@ -52,6 +52,17 @@ public CompletionItemKind Kind set; } = CompletionItemKind.None; + /// + /// Tags for this completion item. + /// + [DataMember(Name = "tags")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public CompletionItemTag[]? Tags + { + get; + set; + } + /// /// Gets or sets the completion detail. /// diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs new file mode 100644 index 0000000000000..a1d482e7a480a --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol +{ + using Newtonsoft.Json; + + /// + /// Utilities to aid work with VS Code LSP Extensions. + /// + internal static class VSCodeInternalExtensionUtilities + { + /// + /// Adds necessary to deserialize + /// JSON stream into objects which include VS Code-specific extensions. + /// + /// + /// If is used in parallel to execution of this method, + /// its access needs to be synchronized with this method call, to guarantee that + /// collection is not modified when in use. + /// + /// Instance of which is guaranteed to not work in parallel to this method call. + public static void AddVSCodeInternalExtensionConverters(this JsonSerializer serializer) + { + // Reading the number of converters before we start adding new ones + var existingConvertersCount = serializer.Converters.Count; + + AddOrReplaceConverter(); + AddOrReplaceConverter(); + + void AddOrReplaceConverter() + where TExtension : TBase + { + for (var i = 0; i < existingConvertersCount; i++) + { + var existingConverterType = serializer.Converters[i].GetType(); + if (existingConverterType.IsGenericType && + existingConverterType.GetGenericTypeDefinition() == typeof(VSExtensionConverter<,>) && + existingConverterType.GenericTypeArguments[0] == typeof(TBase)) + { + serializer.Converters.RemoveAt(i); + existingConvertersCount--; + break; + } + } + + serializer.Converters.Add(new VSExtensionConverter()); + } + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/Diagnostics/VSInternalDiagnosticKind.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/Diagnostics/VSInternalDiagnosticKind.cs index 05d65708fcfd3..2e2756573aa8e 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/Diagnostics/VSInternalDiagnosticKind.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/Diagnostics/VSInternalDiagnosticKind.cs @@ -21,5 +21,10 @@ internal readonly record struct VSInternalDiagnosticKind(string Value) : IString /// Task list diagnostic kind. /// public static readonly VSInternalDiagnosticKind Task = new("task"); + + /// + /// Edit and Continue diagnostic kind. + /// + public static readonly VSInternalDiagnosticKind EditAndContiue = new("enc"); } } diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/Efficiency/OptimizedVSCompletionListJsonConverter.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/Efficiency/OptimizedVSCompletionListJsonConverter.cs index 8369864bfd38f..2dbe0839d6d76 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/Efficiency/OptimizedVSCompletionListJsonConverter.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/Efficiency/OptimizedVSCompletionListJsonConverter.cs @@ -176,6 +176,12 @@ private static void WriteCompletionItem(JsonWriter writer, CompletionItem comple writer.WritePropertyName("kind"); writer.WriteValue(completionItem.Kind); + if (completionItem.Tags != null) + { + writer.WritePropertyName("tags"); + serializer.Serialize(writer, completionItem.Tags); + } + if (completionItem.Detail != null) { writer.WritePropertyName("detail"); diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalHover.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalHover.cs index ea9a5ec9094f6..de87ad88193bc 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalHover.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalHover.cs @@ -10,6 +10,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// Extension to Hover which adds additional data for colorization. /// + [DataContract] internal class VSInternalHover : Hover { /// diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionContext.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionContext.cs index 284ffa5d505ae..a3ab7943d4821 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionContext.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionContext.cs @@ -12,6 +12,7 @@ namespace Roslyn.LanguageServer.Protocol /// Context for inline completion request. /// See https://github.com/microsoft/vscode/blob/075ba020e8493f40dba89891b1a08453f2c067e9/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts#L27. /// + [DataContract] internal class VSInternalInlineCompletionContext { /// diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionItem.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionItem.cs index 73fb5c5285852..740c6799c26d9 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionItem.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionItem.cs @@ -13,6 +13,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// See https://github.com/microsoft/vscode/blob/075ba020e8493f40dba89891b1a08453f2c067e9/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts#L78. /// + [DataContract] internal class VSInternalInlineCompletionItem { /// @@ -45,4 +46,4 @@ internal class VSInternalInlineCompletionItem [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public InsertTextFormat? TextFormat { get; set; } = InsertTextFormat.Plaintext; } -} \ No newline at end of file +} diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionList.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionList.cs index 62f4b20ba0d07..57c7957df661b 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionList.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionList.cs @@ -12,6 +12,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// See https://github.com/microsoft/vscode/blob/075ba020e8493f40dba89891b1a08453f2c067e9/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts#L72. /// + [DataContract] internal class VSInternalInlineCompletionList { /// @@ -21,4 +22,4 @@ internal class VSInternalInlineCompletionList [JsonProperty(Required = Required.Always)] public VSInternalInlineCompletionItem[] Items { get; set; } } -} \ No newline at end of file +} diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionRequest.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionRequest.cs index fe3840400b762..a57c4f294d4db 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionRequest.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionRequest.cs @@ -12,6 +12,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// See https://github.com/microsoft/vscode/blob/075ba020e8493f40dba89891b1a08453f2c067e9/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts#L24. /// + [DataContract] internal class VSInternalInlineCompletionRequest : ITextDocumentParams { /// diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalReferenceParams.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalReferenceParams.cs index a302b2baeb3ce..ef0a031bbb6dd 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalReferenceParams.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalReferenceParams.cs @@ -10,6 +10,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// Class which represents extensions of passed as parameter of find reference requests. /// + [DataContract] internal class VSInternalReferenceParams : ReferenceParams { /// diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalSelectedCompletionInfo.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalSelectedCompletionInfo.cs index 566acebde174c..963cc264e0acf 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalSelectedCompletionInfo.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalSelectedCompletionInfo.cs @@ -12,6 +12,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// See https://github.com/microsoft/vscode/blob/075ba020e8493f40dba89891b1a08453f2c067e9/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts#L48. /// + [DataContract] internal class VSInternalSelectedCompletionInfo { /// @@ -42,4 +43,4 @@ internal class VSInternalSelectedCompletionInfo [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public bool IsSnippetText { get; set; } } -} \ No newline at end of file +} diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalTextDocumentClientCapabilities.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalTextDocumentClientCapabilities.cs index 1ae592269a85e..abc8b2b3749ed 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalTextDocumentClientCapabilities.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalTextDocumentClientCapabilities.cs @@ -10,6 +10,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// Text document capabilities specific to Visual Studio. /// + [DataContract] internal class VSInternalTextDocumentClientCapabilities : TextDocumentClientCapabilities { /// diff --git a/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs b/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs index 3303971dcb38d..8570c4d0db31b 100644 --- a/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs +++ b/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs @@ -11,6 +11,8 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.ServerLifetime; using Microsoft.CommonLanguageServerProtocol.Framework; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; using StreamJsonRpc; @@ -26,16 +28,19 @@ internal sealed class RoslynLanguageServer : AbstractLanguageServer supportedLanguages, WellKnownLspServerKinds serverKind) - : base(jsonRpc, logger) + : base(jsonRpc, serializer, logger) { _lspServiceProvider = lspServiceProvider; _serverKind = serverKind; + VSCodeInternalExtensionUtilities.AddVSCodeInternalExtensionConverters(serializer); + // Create services that require base dependencies (jsonrpc) or are more complex to create to the set manually. _baseServices = GetBaseServices(jsonRpc, logger, capabilitiesProvider, hostServices, serverKind, supportedLanguages); @@ -51,7 +56,7 @@ protected override ILspServices ConstructLspServices() protected override IRequestExecutionQueue ConstructRequestExecutionQueue() { var provider = GetLspServices().GetRequiredService>(); - return provider.CreateRequestExecutionQueue(this, _logger, HandlerProvider); + return provider.CreateRequestExecutionQueue(this, Logger, HandlerProvider); } private ImmutableDictionary>> GetBaseServices( @@ -107,5 +112,71 @@ public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestCon OnInitialized(); return Task.CompletedTask; } + + protected override string GetLanguageForRequest(string methodName, JToken? parameters) + { + if (parameters == null) + { + Logger.LogInformation("No request parameters given, using default language handler"); + return LanguageServerConstants.DefaultLanguageName; + } + + // For certain requests like text syncing we'll always use the default language handler + // as we do not want languages to be able to override them. + if (ShouldUseDefaultLanguage(methodName)) + { + return LanguageServerConstants.DefaultLanguageName; + } + + var lspWorkspaceManager = GetLspServices().GetRequiredService(); + + // All general LSP spec document params have the following json structure + // { "textDocument": { "uri": "" ... } ... } + // + // We can easily identify the URI for the request by looking for this structure + var textDocumentToken = parameters["textDocument"] ?? parameters["_vs_textDocument"]; + if (textDocumentToken is not null) + { + var uriToken = textDocumentToken["uri"]; + Contract.ThrowIfNull(uriToken, "textDocument does not have a uri property"); + var uri = uriToken.ToObject(_jsonSerializer); + Contract.ThrowIfNull(uri, "Failed to deserialize uri property"); + var language = lspWorkspaceManager.GetLanguageForUri(uri); + Logger.LogInformation($"Using {language} from request text document"); + return language; + } + + // All the LSP resolve params have the following known json structure + // { "data": { "TextDocument": { "uri": "" ... } ... } ... } + // + // We can deserialize the data object using our unified DocumentResolveData. + var dataToken = parameters["data"]; + if (dataToken is not null) + { + var data = dataToken.ToObject(_jsonSerializer); + Contract.ThrowIfNull(data, "Failed to document resolve data object"); + var language = lspWorkspaceManager.GetLanguageForUri(data.TextDocument.Uri); + Logger.LogInformation($"Using {language} from data text document"); + return language; + } + + // This request is not for a textDocument and is not a resolve request. + Logger.LogInformation("Request did not contain a textDocument, using default language handler"); + return LanguageServerConstants.DefaultLanguageName; + + static bool ShouldUseDefaultLanguage(string methodName) + => methodName switch + { + Methods.InitializeName => true, + Methods.InitializedName => true, + Methods.TextDocumentDidOpenName => true, + Methods.TextDocumentDidChangeName => true, + Methods.TextDocumentDidCloseName => true, + Methods.TextDocumentDidSaveName => true, + Methods.ShutdownName => true, + Methods.ExitName => true, + _ => false, + }; + } } } diff --git a/src/Features/LanguageServer/Protocol/RoslynRequestExecutionQueue.cs b/src/Features/LanguageServer/Protocol/RoslynRequestExecutionQueue.cs index 1944086af9b1e..291c908c88242 100644 --- a/src/Features/LanguageServer/Protocol/RoslynRequestExecutionQueue.cs +++ b/src/Features/LanguageServer/Protocol/RoslynRequestExecutionQueue.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. -using System; using System.Globalization; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CommonLanguageServerProtocol.Framework; -using Roslyn.LanguageServer.Protocol; -using Newtonsoft.Json.Linq; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer @@ -16,7 +13,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer internal sealed class RoslynRequestExecutionQueue : RequestExecutionQueue { private readonly IInitializeManager _initializeManager; - private readonly LspWorkspaceManager _lspWorkspaceManager; /// /// Serial access is guaranteed by the queue. @@ -27,7 +23,6 @@ public RoslynRequestExecutionQueue(AbstractLanguageServer langua : base(languageServer, logger, handlerProvider) { _initializeManager = languageServer.GetLspServices().GetRequiredService(); - _lspWorkspaceManager = languageServer.GetLspServices().GetRequiredService(); } public override Task WrapStartRequestTaskAsync(Task nonMutatingRequestTask, bool rethrowExceptions) @@ -44,50 +39,6 @@ public override Task WrapStartRequestTaskAsync(Task nonMutatingRequestTask, bool } } - protected override string GetLanguageForRequest(string methodName, TRequest request) - { - var uri = GetUriForRequest(methodName, request); - if (uri is not null) - { - return _lspWorkspaceManager.GetLanguageForUri(uri); - } - - return base.GetLanguageForRequest(methodName, request); - } - - private static Uri? GetUriForRequest(string methodName, TRequest request) - { - if (request is ITextDocumentParams textDocumentParams) - { - return textDocumentParams.TextDocument.Uri; - } - - if (IsDocumentResolveMethod(methodName)) - { - var dataToken = (JToken?)request?.GetType().GetProperty("Data")?.GetValue(request); - var resolveData = dataToken?.ToObject(); - if (resolveData is null) - { - throw new InvalidOperationException($"{methodName} requires resolve data object to derive from {nameof(DocumentResolveData)}."); - } - - return resolveData.TextDocument.Uri; - } - - return null; - - static bool IsDocumentResolveMethod(string methodName) - => methodName switch - { - Methods.CodeActionResolveName => true, - Methods.CodeLensResolveName => true, - Methods.DocumentLinkResolveName => true, - Methods.InlayHintResolveName => true, - Methods.TextDocumentCompletionResolveName => true, - _ => false, - }; - } - /// /// Serial access is guaranteed by the queue. /// diff --git a/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs b/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs index 1a1ef3f6833d0..289f609e7df67 100644 --- a/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs +++ b/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs @@ -203,7 +203,7 @@ public void UpdateTrackedDocument(Uri uri, SourceText newSourceText) // Ensure we have the latest lsp solutions var updatedSolutions = await GetLspSolutionsAsync(cancellationToken).ConfigureAwait(false); - var (hostWorkspace, hostWorkspaceSolution, isForked) = updatedSolutions.FirstOrDefault(lspSolution => lspSolution.Solution.WorkspaceKind == WorkspaceKind.Host); + var (hostWorkspace, hostWorkspaceSolution, isForked) = updatedSolutions.FirstOrDefault(lspSolution => lspSolution.Solution.WorkspaceKind is WorkspaceKind.Host); _requestTelemetryLogger.UpdateUsedForkedSolutionCounter(isForked); return (hostWorkspace, hostWorkspaceSolution); @@ -274,7 +274,7 @@ public void UpdateTrackedDocument(Uri uri, SourceText newSourceText) .Concat(registeredWorkspaces.Where(workspace => workspace.Kind == WorkspaceKind.MiscellaneousFiles)) .ToImmutableArray(); - using var _ = ArrayBuilder<(Workspace, Solution, bool)>.GetInstance(out var solutions); + var solutions = new FixedSizeArrayBuilder<(Workspace, Solution, bool)>(registeredWorkspaces.Length); foreach (var workspace in registeredWorkspaces) { // Retrieve the workspace's current view of the world at the time the request comes in. If this is changing @@ -285,7 +285,7 @@ public void UpdateTrackedDocument(Uri uri, SourceText newSourceText) solutions.Add((workspace, lspSolution, isForked)); } - return solutions.ToImmutable(); + return solutions.MoveToImmutable(); async Task<(Solution Solution, bool IsForked)> GetLspSolutionForWorkspaceAsync(Workspace workspace, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationEventListener.cs b/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationEventListener.cs index 31071d7b54cc4..d9b81548650c5 100644 --- a/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationEventListener.cs +++ b/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationEventListener.cs @@ -14,7 +14,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer; WorkspaceKind.Host, WorkspaceKind.MiscellaneousFiles, WorkspaceKind.MetadataAsSource, - WorkspaceKind.Interactive), Shared] + WorkspaceKind.Interactive, + WorkspaceKind.SemanticSearch), Shared] internal sealed class LspWorkspaceRegistrationEventListener : IEventListener, IEventListenerStoppable { private readonly LspWorkspaceRegistrationService _lspWorkspaceRegistrationService; diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs index 97d60c6a0957c..b2b813d4c07ed 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs @@ -390,7 +390,7 @@ void M() await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = clientCapability, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspMockCompletionService.Factory) }.ToList()); + Composition.AddParts(typeof(CSharpLspMockCompletionService.Factory))); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspMockCompletionService; mockService.NonDefaultRule = CompletionItemRules.Default.WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, ' ', '(')); @@ -437,7 +437,7 @@ public async Task TestUsingServerDefaultCommitCharacters(bool mutatingLspWorkspa var markup = "Item{|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspMockCompletionService.Factory) }.ToList()); + composition: Composition.AddParts(typeof(CSharpLspMockCompletionService.Factory))); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspMockCompletionService; mockService.NonDefaultRule = CompletionItemRules.Default.WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, ' ', '(')); @@ -496,6 +496,79 @@ public async Task TestUsingServerDefaultCommitCharacters(bool mutatingLspWorkspa } } + [Theory] + [CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/26488")] + public async Task TestCompletionForObsoleteSymbol(bool mutatingLspWorkspace) + { + var markup = + """ + using System; + + [Obsolete] + class ObsoleteType; + + class A + { + void M() + { + ObsoleteType{|caret:|} + } + } + """; + + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, DefaultClientCapabilities); + var completionParams = CreateCompletionParams( + testLspServer.GetLocations("caret").Single(), + invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, + triggerCharacter: "\0", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + + var completionResult = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionName, completionParams, CancellationToken.None).ConfigureAwait(false); + Assert.NotNull(completionResult.ItemDefaults.EditRange); + Assert.NotNull(completionResult.ItemDefaults.Data); + Assert.NotNull(completionResult.ItemDefaults.CommitCharacters); + + var actualItem = completionResult.Items.First(i => i.Label == "ObsoleteType"); + Assert.Null(actualItem.LabelDetails); + Assert.Null(actualItem.SortText); + Assert.Equal(CompletionItemKind.Class, actualItem.Kind); + Assert.Equal([CompletionItemTag.Deprecated], actualItem.Tags); + Assert.Null(actualItem.FilterText); + Assert.Null(actualItem.TextEdit); + Assert.Null(actualItem.TextEditText); + Assert.Null(actualItem.AdditionalTextEdits); + Assert.Null(actualItem.Command); + Assert.Null(actualItem.CommitCharacters); + Assert.Null(actualItem.Data); + Assert.Null(actualItem.Detail); + Assert.Null(actualItem.Documentation); + + actualItem.Data = completionResult.ItemDefaults.Data; + + var resolvedItem = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionResolveName, actualItem, CancellationToken.None).ConfigureAwait(false); + Assert.Null(resolvedItem.LabelDetails); + Assert.Null(resolvedItem.SortText); + Assert.Equal(CompletionItemKind.Class, resolvedItem.Kind); + Assert.Equal([CompletionItemTag.Deprecated], resolvedItem.Tags); + + Assert.Null(resolvedItem.AdditionalTextEdits); + Assert.Null(resolvedItem.FilterText); + Assert.Null(resolvedItem.TextEdit); + Assert.Null(resolvedItem.TextEditText); + Assert.Null(resolvedItem.Command); + Assert.Null(resolvedItem.Detail); + + var expectedDocumentation = new MarkupContent() + { + Kind = LSP.MarkupKind.PlainText, + Value = "[deprecated] class ObsoleteType" + }; + AssertJsonEquals(resolvedItem.Documentation, expectedDocumentation); + } + private sealed class CSharpLspMockCompletionService : CompletionService { private CSharpLspMockCompletionService(SolutionServices services, IAsynchronousOperationListenerProvider listenerProvider) : base(services, listenerProvider) @@ -692,7 +765,7 @@ public async Task TestSoftSelectionWhenFilterTextIsEmptyForPreselectItemAsync(bo var markup = "{|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspMockCompletionService.Factory) }.ToList()); + composition: Composition.AddParts(typeof(CSharpLspMockCompletionService.Factory))); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspMockCompletionService; mockService.NonDefaultRule = CompletionItemRules.Default.WithMatchPriority(MatchPriority.Preselect); @@ -800,7 +873,7 @@ public async Task TestHandleExceptionFromGetCompletionChange(bool mutatingLspWor var markup = "Item {|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspThrowExceptionOnChangeCompletionService.Factory) }.ToList()); + composition: Composition.AddParts(typeof(CSharpLspThrowExceptionOnChangeCompletionService.Factory))); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspThrowExceptionOnChangeCompletionService; var builder = ImmutableArray.CreateBuilder(); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs b/src/Features/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs index 5baca59a8dffb..728291fc02f5c 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs @@ -157,7 +157,7 @@ private static void VerifyValuesInServer(EditorTestWorkspace workspace, List option is IPerLanguageValuedOption ? 2 : 1), expectedValues.Count); var optionsAndLanguageToVerify = supportedOptions.SelectManyAsArray(option => option is IPerLanguageValuedOption ? DidChangeConfigurationNotificationHandler.SupportedLanguages.SelectAsArray(lang => (option, lang)) - : SpecializedCollections.SingletonEnumerable((option, string.Empty))); + : [(option, string.Empty)]); for (var i = 0; i < expectedValues.Count; i++) { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index fcc57e1a545ce..556c9a4cc1282 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -35,12 +35,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics using DocumentDiagnosticPartialReport = SumType; using WorkspaceDiagnosticPartialReport = SumType; - public abstract class AbstractPullDiagnosticTestsBase : AbstractLanguageServerProtocolTests + public abstract class AbstractPullDiagnosticTestsBase(ITestOutputHelper testOutputHelper) : AbstractLanguageServerProtocolTests(testOutputHelper) { - protected AbstractPullDiagnosticTestsBase(ITestOutputHelper testOutputHelper) : base(testOutputHelper) - { - } - private protected override TestAnalyzerReferenceByLanguage CreateTestAnalyzersReference() { var builder = ImmutableDictionary.CreateBuilder>(); @@ -76,7 +72,7 @@ private protected static async Task> RunGet } else { - return await RunPublicGetWorkspacePullDiagnosticsAsync(testLspServer, previousResults, useProgress, triggerConnectionClose); + return await RunPublicGetWorkspacePullDiagnosticsAsync(testLspServer, previousResults, useProgress, category, triggerConnectionClose); } } @@ -118,6 +114,7 @@ private protected static async Task> RunPub TestLspServer testLspServer, ImmutableArray<(string resultId, TextDocumentIdentifier identifier)>? previousResults, bool useProgress, + string? category, bool triggerConnectionClose) { await testLspServer.WaitForDiagnosticsAsync(); @@ -125,7 +122,7 @@ private protected static async Task> RunPub BufferedProgress? progress = useProgress ? BufferedProgress.Create(null) : null; var diagnosticsTask = testLspServer.ExecuteRequestAsync( Methods.WorkspaceDiagnosticName, - CreateProposedWorkspaceDiagnosticParams(previousResults, progress), + CreateProposedWorkspaceDiagnosticParams(previousResults, progress, category), CancellationToken.None).ConfigureAwait(false); if (triggerConnectionClose) @@ -151,8 +148,9 @@ private protected static async Task> RunPub } private static WorkspaceDiagnosticParams CreateProposedWorkspaceDiagnosticParams( - ImmutableArray<(string resultId, TextDocumentIdentifier identifier)>? previousResults = null, - IProgress? progress = null) + ImmutableArray<(string resultId, TextDocumentIdentifier identifier)>? previousResults, + IProgress? progress, + string? category) { var previousResultsLsp = previousResults?.Select(r => new PreviousResultId { @@ -162,7 +160,8 @@ private static WorkspaceDiagnosticParams CreateProposedWorkspaceDiagnosticParams return new WorkspaceDiagnosticParams { PreviousResultId = previousResultsLsp, - PartialResultToken = progress + PartialResultToken = progress, + Identifier = category }; } @@ -170,12 +169,12 @@ private static TestDiagnosticResult ConvertWorkspaceDiagnosticResult(SumType CreateDiagnosticParamsFromPreviousReports(ImmutableArray results) { - - return results.Select(r => (r.ResultId, r.TextDocument)).ToImmutableArray(); + // If there was no resultId provided in the response, we cannot create previous results for it. + return results.Where(r => r.ResultId != null).Select(r => (r.ResultId!, r.TextDocument)).ToImmutableArray(); } private protected static VSInternalDocumentDiagnosticsParams CreateDocumentDiagnosticParams( @@ -235,10 +234,9 @@ private protected static Task> RunGetDocume bool useVSDiagnostics, string? previousResultId = null, bool useProgress = false, - string? category = null, - bool testNonLocalDiagnostics = false) + string? category = null) { - return RunGetDocumentPullDiagnosticsAsync(testLspServer, new VSTextDocumentIdentifier { Uri = uri }, useVSDiagnostics, previousResultId, useProgress, category, testNonLocalDiagnostics); + return RunGetDocumentPullDiagnosticsAsync(testLspServer, new VSTextDocumentIdentifier { Uri = uri }, useVSDiagnostics, previousResultId, useProgress, category); } private protected static async Task> RunGetDocumentPullDiagnosticsAsync( @@ -247,14 +245,13 @@ private protected static async Task> RunGet bool useVSDiagnostics, string? previousResultId = null, bool useProgress = false, - string? category = null, - bool testNonLocalDiagnostics = false) + string? category = null) { await testLspServer.WaitForDiagnosticsAsync(); if (useVSDiagnostics) { - Assert.False(testNonLocalDiagnostics, "NonLocalDiagnostics are only supported for public DocumentPullHandler"); + Assert.False(category == PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal, "NonLocalDiagnostics are only supported for public DocumentPullHandler"); BufferedProgress? progress = useProgress ? BufferedProgress.Create(null) : null; var diagnostics = await testLspServer.ExecuteRequestAsync( VSInternalMethods.DocumentPullDiagnosticName, @@ -275,7 +272,7 @@ private protected static async Task> RunGet BufferedProgress? progress = useProgress ? BufferedProgress.Create(null) : null; var diagnostics = await testLspServer.ExecuteRequestAsync?>( Methods.TextDocumentDiagnosticName, - CreateProposedDocumentDiagnosticParams(vsTextDocumentIdentifier, previousResultId, progress, testNonLocalDiagnostics), + CreateProposedDocumentDiagnosticParams(vsTextDocumentIdentifier, previousResultId, category, progress), CancellationToken.None).ConfigureAwait(false); if (useProgress) { @@ -302,12 +299,12 @@ private protected static async Task> RunGet static DocumentDiagnosticParams CreateProposedDocumentDiagnosticParams( VSTextDocumentIdentifier vsTextDocumentIdentifier, string? previousResultId, - IProgress? progress, - bool testNonLocalDiagnostics) + string? category, + IProgress? progress) { return new DocumentDiagnosticParams { - Identifier = testNonLocalDiagnostics ? DocumentPullDiagnosticHandler.DocumentNonLocalDiagnosticIdentifier.ToString() : null, + Identifier = category, PreviousResultId = previousResultId, PartialResultToken = progress, TextDocument = vsTextDocumentIdentifier, @@ -365,7 +362,7 @@ private protected static InitializationOptions GetInitializationOptions( /// Helper type to store unified LSP diagnostic results. /// Diagnostics are null when unchanged. /// - private protected record TestDiagnosticResult(TextDocumentIdentifier TextDocument, string ResultId, LSP.Diagnostic[]? Diagnostics) + private protected record TestDiagnosticResult(TextDocumentIdentifier TextDocument, string? ResultId, LSP.Diagnostic[]? Diagnostics) { public Uri Uri { get; } = TextDocument.Uri; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs new file mode 100644 index 0000000000000..cb85dd18a0c98 --- /dev/null +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs @@ -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. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; +using Newtonsoft.Json.Linq; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Test.Utilities; +using StreamJsonRpc; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics; + +public class DiagnosticRegistrationTests : AbstractLanguageServerProtocolTests +{ + public DiagnosticRegistrationTests(ITestOutputHelper? testOutputHelper) : base(testOutputHelper) + { + } + + [Theory, CombinatorialData] + public async Task TestPublicDiagnosticSourcesAreRegisteredWhenSupported(bool mutatingLspWorkspace) + { + var clientCapabilities = new ClientCapabilities + { + TextDocument = new TextDocumentClientCapabilities + { + Diagnostic = new DiagnosticSetting + { + DynamicRegistration = true, + } + } + }; + var clientCallbackTarget = new ClientCallbackTarget(); + var initializationOptions = new InitializationOptions() + { + CallInitialized = true, + ClientCapabilities = clientCapabilities, + ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer, + ClientTarget = clientCallbackTarget, + }; + + await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, initializationOptions); + + var registrations = clientCallbackTarget.GetRegistrations(); + + // Get all registrations for diagnostics (note that workspace registrations are registered against document method name). + var diagnosticRegistrations = registrations + .Where(r => r.Method == Methods.TextDocumentDiagnosticName) + .Select(r => ((JObject)r.RegisterOptions!).ToObject()!); + + Assert.NotEmpty(diagnosticRegistrations); + + string[] documentSources = [ + PullDiagnosticCategories.DocumentCompilerSyntax, + PullDiagnosticCategories.DocumentCompilerSemantic, + PullDiagnosticCategories.DocumentAnalyzerSyntax, + PullDiagnosticCategories.DocumentAnalyzerSemantic, + PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal + ]; + + string[] documentAndWorkspaceSources = [ + PullDiagnosticCategories.EditAndContinue, + PullDiagnosticCategories.WorkspaceDocumentsAndProject + ]; + + // Verify document only sources are present (and do not set the workspace diagnostic option). + foreach (var documentSource in documentSources) + { + var options = Assert.Single(diagnosticRegistrations, (r) => r.Identifier == documentSource); + Assert.False(options.WorkspaceDiagnostics); + Assert.True(options.InterFileDependencies); + } + + // Verify workspace sources are present (and do set the workspace diagnostic option). + foreach (var workspaceSource in documentAndWorkspaceSources) + { + var options = Assert.Single(diagnosticRegistrations, (r) => r.Identifier == workspaceSource); + Assert.True(options.WorkspaceDiagnostics); + Assert.True(options.InterFileDependencies); + Assert.True(options.WorkDoneProgress); + } + + // Verify task diagnostics are not present. + Assert.DoesNotContain(diagnosticRegistrations, (r) => r.Identifier == PullDiagnosticCategories.Task); + } + + /// + /// Implements a client side callback target for client/registerCapability to inspect what was registered. + /// + private class ClientCallbackTarget() + { + private readonly List _registrations = new(); + + [JsonRpcMethod(Methods.ClientRegisterCapabilityName, UseSingleObjectParameterDeserialization = true)] + public void ClientRegisterCapability(RegistrationParams registrationParams, CancellationToken _) + { + _registrations.AddRange(registrationParams.Registrations); + } + + /// + /// This is safe to call after 'initialized' has completed because capabilties are dynamically registered in the + /// implementation of the initialized request. Additionally, client/registerCapability is a request (not a notification) + /// which means the server will wait for the client to finish handling it before the server returns from 'initialized'. + /// + public ImmutableArray GetRegistrations() + { + return _registrations.ToImmutableArray(); + } + } +} diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs index 5f9e9694c4ec4..24b0be51a26dc 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; @@ -39,7 +40,7 @@ internal async Task TestNonLocalDocumentDiagnosticsAreReportedWhenFSAEnabled(boo // and not reported here. await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, testNonLocalDiagnostics: true); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, category: PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal); if (fsaEnabled) { Assert.Equal(1, results.Length); @@ -50,7 +51,7 @@ internal async Task TestNonLocalDocumentDiagnosticsAreReportedWhenFSAEnabled(boo Assert.Equal(document.GetURI(), results[0].Uri); // Asking again should give us back unchanged diagnostics. - var results2 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, previousResultId: results.Single().ResultId, testNonLocalDiagnostics: true); + var results2 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, previousResultId: results.Single().ResultId, category: PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal); Assert.Null(results2[0].Diagnostics); Assert.Equal(results[0].ResultId, results2[0].ResultId); } @@ -59,7 +60,7 @@ internal async Task TestNonLocalDocumentDiagnosticsAreReportedWhenFSAEnabled(boo Assert.Empty(results); // Asking again should give us back unchanged diagnostics. - var results2 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, testNonLocalDiagnostics: true); + var results2 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, category: PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal); Assert.Empty(results2); } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index 63cb36498c2ad..1141ce6e1a12d 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -5,10 +5,14 @@ using System; using System.Collections.Immutable; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; @@ -22,429 +26,394 @@ using Xunit.Abstractions; using LSP = Roslyn.LanguageServer.Protocol; -namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics -{ - public class PullDiagnosticTests : AbstractPullDiagnosticTestsBase - { - public PullDiagnosticTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) - { - } +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics; - #region Document Diagnostics +public sealed class PullDiagnosticTests(ITestOutputHelper testOutputHelper) : AbstractPullDiagnosticTestsBase(testOutputHelper) +{ + #region Document Diagnostics - [Theory, CombinatorialData] - public async Task TestNoDocumentDiagnosticsForClosedFilesWithFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestNoDocumentDiagnosticsForClosedFilesWithFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Empty(results); + Assert.Empty(results); - // Verify document pull diagnostics are unaffected by running code analysis. - await testLspServer.RunCodeAnalysisAsync(document.Project.Id); - results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Empty(results); - } + // Verify document pull diagnostics are unaffected by running code analysis. + await testLspServer.RunCodeAnalysisAsync(document.Project.Id); + results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); + Assert.Empty(results); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); - } + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); + } - [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/fsharp/issues/15972")] - public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/fsharp/issues/15972")] + public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A : B {"; - var additionalAnalyzers = new DiagnosticAnalyzer[] { new CSharpSyntaxAnalyzer(), new CSharpSemanticAnalyzer() }; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true, additionalAnalyzers: additionalAnalyzers); + var additionalAnalyzers = new DiagnosticAnalyzer[] { new CSharpSyntaxAnalyzer(), new CSharpSemanticAnalyzer() }; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, additionalAnalyzers: additionalAnalyzers); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var syntaxResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentCompilerSyntax); + var syntaxResults = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentCompilerSyntax); - var semanticResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentCompilerSemantic); + var semanticResults = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentCompilerSemantic); - Assert.Equal("CS1513", syntaxResults.Single().Diagnostics.Single().Code); - Assert.Equal("CS0246", semanticResults.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", syntaxResults.Single().Diagnostics.Single().Code); + Assert.Equal("CS0246", semanticResults.Single().Diagnostics.Single().Code); - var syntaxResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: syntaxResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSyntax); - var semanticResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: semanticResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSemantic); + var syntaxResults2 = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: syntaxResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSyntax); + var semanticResults2 = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: semanticResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSemantic); - Assert.Equal(syntaxResults.Single().ResultId, syntaxResults2.Single().ResultId); - Assert.Equal(semanticResults.Single().ResultId, semanticResults2.Single().ResultId); + Assert.Equal(syntaxResults.Single().ResultId, syntaxResults2.Single().ResultId); + Assert.Equal(semanticResults.Single().ResultId, semanticResults2.Single().ResultId); - var syntaxAnalyzerResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); + var syntaxAnalyzerResults = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); - var semanticAnalyzerResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); + var semanticAnalyzerResults = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); - Assert.Equal(CSharpSyntaxAnalyzer.RuleId, syntaxAnalyzerResults.Single().Diagnostics.Single().Code); - Assert.Equal(CSharpSemanticAnalyzer.RuleId, semanticAnalyzerResults.Single().Diagnostics.Single().Code); + Assert.Equal(CSharpSyntaxAnalyzer.RuleId, syntaxAnalyzerResults.Single().Diagnostics.Single().Code); + Assert.Equal(CSharpSemanticAnalyzer.RuleId, semanticAnalyzerResults.Single().Diagnostics.Single().Code); - var syntaxAnalyzerResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: syntaxAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); - var semanticAnalyzerResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: semanticAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); + var syntaxAnalyzerResults2 = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: syntaxAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); + var semanticAnalyzerResults2 = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: semanticAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); - Assert.Equal(syntaxAnalyzerResults.Single().ResultId, syntaxAnalyzerResults2.Single().ResultId); - Assert.Equal(semanticAnalyzerResults.Single().ResultId, semanticAnalyzerResults2.Single().ResultId); - } + Assert.Equal(syntaxAnalyzerResults.Single().ResultId, syntaxAnalyzerResults2.Single().ResultId); + Assert.Equal(semanticAnalyzerResults.Single().ResultId, semanticAnalyzerResults2.Single().ResultId); + } - private sealed class CSharpSyntaxAnalyzer : DiagnosticAnalyzer - { - public const string RuleId = "SYN0001"; - private readonly DiagnosticDescriptor _descriptor = new(RuleId, "Title", "Message", "Category", DiagnosticSeverity.Warning, isEnabledByDefault: true); + private sealed class CSharpSyntaxAnalyzer : DiagnosticAnalyzer + { + public const string RuleId = "SYN0001"; + private readonly DiagnosticDescriptor _descriptor = new(RuleId, "Title", "Message", "Category", DiagnosticSeverity.Warning, isEnabledByDefault: true); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(_descriptor); + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(_descriptor); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxTreeAction(context => - context.ReportDiagnostic(Diagnostic.Create(_descriptor, context.Tree.GetRoot(context.CancellationToken).GetLocation()))); - } + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxTreeAction(context => + context.ReportDiagnostic(Diagnostic.Create(_descriptor, context.Tree.GetRoot(context.CancellationToken).GetLocation()))); } + } - private sealed class CSharpSemanticAnalyzer : DiagnosticAnalyzer - { - public const string RuleId = "SEM0001"; - private readonly DiagnosticDescriptor _descriptor = new(RuleId, "Title", "Message", "Category", DiagnosticSeverity.Warning, isEnabledByDefault: true); + private sealed class CSharpSemanticAnalyzer : DiagnosticAnalyzer + { + public const string RuleId = "SEM0001"; + private readonly DiagnosticDescriptor _descriptor = new(RuleId, "Title", "Message", "Category", DiagnosticSeverity.Warning, isEnabledByDefault: true); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(_descriptor); + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(_descriptor); - public override void Initialize(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(context => - context.ReportDiagnostic(Diagnostic.Create(_descriptor, context.Node.GetLocation())), - CSharp.SyntaxKind.CompilationUnit); - } + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(context => + context.ReportDiagnostic(Diagnostic.Create(_descriptor, context.Node.GetLocation())), + CSharp.SyntaxKind.CompilationUnit); } + } - [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/65172")] - public async Task TestDocumentDiagnosticsHasVSExpandedMessage(bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/65172")] + public async Task TestDocumentDiagnosticsHasVSExpandedMessage(bool mutatingLspWorkspace) + { + var markup = @"internal class Program { static void Main(string[] args) { } }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - await OpenDocumentAsync(testLspServer, document); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true); + await OpenDocumentAsync(testLspServer, document); - Assert.Equal("IDE0060", results.Single().Diagnostics.Single().Code); - var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics.Single(); - Assert.Equal(vsDiagnostic.ExpandedMessage, AnalyzersResources.Avoid_unused_parameters_in_your_code_If_the_parameter_cannot_be_removed_then_change_its_name_so_it_starts_with_an_underscore_and_is_optionally_followed_by_an_integer_such_as__comma__1_comma__2_etc_These_are_treated_as_special_discard_symbol_names); - } + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics: true); - [Theory, CombinatorialData] - public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile_NoCategory(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = -@" -// todo: goo -class A { -}"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - - await OpenDocumentAsync(testLspServer, document); - - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); - - Assert.Empty(results.Single().Diagnostics); - } + Assert.Equal("IDE0060", results.Single().Diagnostics.Single().Code); + var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics.Single(); + Assert.Equal(vsDiagnostic.ExpandedMessage, AnalyzersResources.Avoid_unused_parameters_in_your_code_If_the_parameter_cannot_be_removed_then_change_its_name_so_it_starts_with_an_underscore_and_is_optionally_followed_by_an_integer_such_as__comma__1_comma__2_etc_These_are_treated_as_special_discard_symbol_names); + } - [Theory, CombinatorialData] - public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile_Category(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile_Category(bool mutatingLspWorkspace) + { + var markup = @" // todo: goo class A { }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.Task); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.Task); - if (useVSDiagnostics) - { - Assert.Equal("TODO", results.Single().Diagnostics.Single().Code); - Assert.Equal("todo: goo", results.Single().Diagnostics.Single().Message); - } - else - { - Assert.Empty(results.Single().Diagnostics); - } - } + Assert.Equal("TODO", results.Single().Diagnostics.Single().Code); + Assert.Equal("todo: goo", results.Single().Diagnostics.Single().Message); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOn(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOn(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, - GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics)); - - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics)); - testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag, true); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - } + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsForRemovedDocument(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsForRemovedDocument(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var workspace = testLspServer.TestWorkspace; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + var workspace = testLspServer.TestWorkspace; - // Calling GetTextBuffer will effectively open the file. - workspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + workspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - // Get the diagnostics for the solution containing the doc. - var solution = document.Project.Solution; + // Get the diagnostics for the solution containing the doc. + var solution = document.Project.Solution; - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics).ConfigureAwait(false); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics).ConfigureAwait(false); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - // Now remove the doc. - workspace.OnDocumentRemoved(workspace.Documents.Single().Id); - await CloseDocumentAsync(testLspServer, document); + // Now remove the doc. + workspace.OnDocumentRemoved(workspace.Documents.Single().Id); + await CloseDocumentAsync(testLspServer, document); - results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, results.Single().ResultId).ConfigureAwait(false); + results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, results.Single().ResultId).ConfigureAwait(false); - Assert.Equal(useVSDiagnostics ? null : [], results.Single().Diagnostics); - Assert.Null(results.Single().ResultId); - } + // VS represents removal with null diagnostics, VS code represents with an empty diagnostics array. + Assert.Equal(useVSDiagnostics ? null : [], results.Single().Diagnostics); + Assert.Null(results.Single().ResultId); + } - [Theory, CombinatorialData] - public async Task TestNoChangeIfDocumentDiagnosticsCalledTwice(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestNoChangeIfDocumentDiagnosticsCalledTwice(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - var resultId = results.Single().ResultId; - results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: resultId); + var resultId = results.Single().ResultId; + results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: resultId); - Assert.Null(results.Single().Diagnostics); - Assert.Equal(resultId, results.Single().ResultId); - } + Assert.Null(results.Single().Diagnostics); + Assert.Equal(resultId, results.Single().ResultId); + } - [Theory, CombinatorialData] - [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1481208")] - public async Task TestDocumentDiagnosticsWhenGlobalStateChanges(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1481208")] + public async Task TestDocumentDiagnosticsWhenGlobalStateChanges(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - var resultId = results.Single().ResultId; + var resultId = results.Single().ResultId; - // Trigger refresh due to a change to global state that affects diagnostics. - var refresher = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); - refresher.RequestWorkspaceRefresh(); + // Trigger refresh due to a change to global state that affects diagnostics. + var refresher = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); + refresher.RequestWorkspaceRefresh(); - results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: resultId); + results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: resultId); - // Result should be different, but diagnostics should be the same - Assert.NotEqual(resultId, results.Single().ResultId); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - } + // Result should be different, but diagnostics should be the same + Assert.NotEqual(resultId, results.Single().ResultId); + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsRemovedAfterErrorIsFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsRemovedAfterErrorIsFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - await InsertTextAsync(testLspServer, document, buffer.CurrentSnapshot.Length, "}"); + await InsertTextAsync(testLspServer, document, buffer.CurrentSnapshot.Length, "}"); - results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, results.Single().ResultId); - Assert.Empty(results[0].Diagnostics); - } + results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, results.Single().ResultId); + Assert.Empty(results[0].Diagnostics); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsRemainAfterErrorIsNotFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsRemainAfterErrorIsNotFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); + await OpenDocumentAsync(testLspServer, document); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); - buffer.Insert(0, " "); - await InsertTextAsync(testLspServer, document, position: 0, text: " "); + buffer.Insert(0, " "); + await InsertTextAsync(testLspServer, document, position: 0, text: " "); - results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), - useVSDiagnostics, - previousResultId: results[0].ResultId); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 10 }, results[0].Diagnostics.Single().Range.Start); - } + results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), + useVSDiagnostics, + previousResultId: results[0].ResultId); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 10 }, results[0].Diagnostics.Single().Range.Start); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsAreNotMapped(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsAreNotMapped(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"#line 1 ""test.txt"" class A {"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.Equal(1, results.Single().Diagnostics.Single().Range.Start.Line); - } + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal(1, results.Single().Diagnostics.Single().Range.Start.Line); + } - [Theory, CombinatorialData] - public async Task TestStreamingDocumentDiagnostics(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestStreamingDocumentDiagnostics(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, useProgress: true); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, useProgress: true); - Assert.Equal("CS1513", results!.Single().Diagnostics.Single().Code); - } + Assert.Equal("CS1513", results!.Single().Diagnostics.Single().Code); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsForOpenFilesUsesActiveContext(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var documentText = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsForOpenFilesUsesActiveContext(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var documentText = @"#if ONE class A { #endif class B {"; - var workspaceXml = + var workspaceXml = @$" {documentText} @@ -454,46 +423,46 @@ class B {"; "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); - var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); + var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); + var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); - // Open either of the documents via LSP, we're tracking the URI and text. - await OpenDocumentAsync(testLspServer, csproj1Document); + // Open either of the documents via LSP, we're tracking the URI and text. + await OpenDocumentAsync(testLspServer, csproj1Document); - // This opens all documents in the workspace and ensures buffers are created. - testLspServer.TestWorkspace.GetTestDocument(csproj1Document.Id)!.GetTextBuffer(); + // This opens all documents in the workspace and ensures buffers are created. + testLspServer.TestWorkspace.GetTestDocument(csproj1Document.Id)!.GetTextBuffer(); - // Set CSProj2 as the active context and get diagnostics. - testLspServer.TestWorkspace.SetDocumentContext(csproj2Document.Id); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj2Document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - if (useVSDiagnostics) - { - // Only VSDiagnostics will have the project. - var vsDiagnostic = (LSP.VSDiagnostic)results.Single().Diagnostics.Single(); - Assert.Equal("CSProj2", vsDiagnostic.Projects.Single().ProjectName); - } - - // Set CSProj1 as the active context and get diagnostics. - testLspServer.TestWorkspace.SetDocumentContext(csproj1Document.Id); - results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); - Assert.Equal(2, results.Single().Diagnostics!.Length); - Assert.All(results.Single().Diagnostics, d => Assert.Equal("CS1513", d.Code)); - - if (useVSDiagnostics) - { - Assert.All(results.Single().Diagnostics, d => Assert.Equal("CSProj1", ((VSDiagnostic)d).Projects.Single().ProjectName)); - } + // Set CSProj2 as the active context and get diagnostics. + testLspServer.TestWorkspace.SetDocumentContext(csproj2Document.Id); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj2Document.GetURI(), useVSDiagnostics); + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + if (useVSDiagnostics) + { + // Only VSDiagnostics will have the project. + var vsDiagnostic = (LSP.VSDiagnostic)results.Single().Diagnostics.Single(); + Assert.Equal("CSProj2", vsDiagnostic.Projects.Single().ProjectName); } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsHasSameIdentifierForLinkedFile(bool mutatingLspWorkspace) + // Set CSProj1 as the active context and get diagnostics. + testLspServer.TestWorkspace.SetDocumentContext(csproj1Document.Id); + results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); + Assert.Equal(2, results.Single().Diagnostics!.Length); + Assert.All(results.Single().Diagnostics, d => Assert.Equal("CS1513", d.Code)); + + if (useVSDiagnostics) { - var documentText = + Assert.All(results.Single().Diagnostics, d => Assert.Equal("CSProj1", ((VSDiagnostic)d).Projects.Single().ProjectName)); + } + } + + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsHasSameIdentifierForLinkedFile(bool mutatingLspWorkspace) + { + var documentText = @"class A { err }"; - var workspaceXml = + var workspaceXml = @$" {documentText} @@ -503,50 +472,50 @@ public async Task TestDocumentDiagnosticsHasSameIdentifierForLinkedFile(bool mut "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: false); + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: false); - var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); - var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); + var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); + var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); - // Open either of the documents via LSP, we're tracking the URI and text. - await OpenDocumentAsync(testLspServer, csproj1Document); + // Open either of the documents via LSP, we're tracking the URI and text. + await OpenDocumentAsync(testLspServer, csproj1Document); - var csproj1Results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, GetVsTextDocumentIdentifier(csproj1Document), useVSDiagnostics: true); - var csproj1Diagnostic = (VSDiagnostic)csproj1Results.Single().Diagnostics.Single(); - var csproj2Results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, GetVsTextDocumentIdentifier(csproj2Document), useVSDiagnostics: true); - var csproj2Diagnostic = (VSDiagnostic)csproj2Results.Single().Diagnostics.Single(); - Assert.Equal(csproj1Diagnostic.Identifier, csproj2Diagnostic.Identifier); + var csproj1Results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, GetVsTextDocumentIdentifier(csproj1Document), useVSDiagnostics: true); + var csproj1Diagnostic = (VSDiagnostic)csproj1Results.Single().Diagnostics.Single(); + var csproj2Results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, GetVsTextDocumentIdentifier(csproj2Document), useVSDiagnostics: true); + var csproj2Diagnostic = (VSDiagnostic)csproj2Results.Single().Diagnostics.Single(); + Assert.Equal(csproj1Diagnostic.Identifier, csproj2Diagnostic.Identifier); - static VSTextDocumentIdentifier GetVsTextDocumentIdentifier(Document document) + static VSTextDocumentIdentifier GetVsTextDocumentIdentifier(Document document) + { + var projectContext = new VSProjectContext { - var projectContext = new VSProjectContext - { - Id = ProtocolConversions.ProjectIdToProjectContextId(document.Project.Id), - Label = document.Project.Name - }; - return new VSTextDocumentIdentifier - { - ProjectContext = projectContext, - Uri = document.GetURI(), - }; - } + Id = ProtocolConversions.ProjectIdToProjectContextId(document.Project.Id), + Label = document.Project.Name + }; + return new VSTextDocumentIdentifier + { + ProjectContext = projectContext, + Uri = document.GetURI(), + }; } + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsWithChangeInReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsWithChangeInReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"namespace M { class A : B { } }"; - var markup2 = + var markup2 = @"namespace M { public class {|caret:|} { } }"; - var workspaceXml = + var workspaceXml = @$" {markup1} @@ -557,43 +526,43 @@ public class {|caret:|} { } "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); - var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); - var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); - - await testLspServer.OpenDocumentAsync(csproj1Document.GetURI()); - await testLspServer.OpenDocumentAsync(csproj2Document.GetURI()); - - // Verify we a diagnostic in A.cs since B does not exist. - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); - Assert.Single(results); - Assert.Equal("CS0246", results.Single().Diagnostics.Single().Code); - - // Insert B into B.cs and verify that the error in A.cs is now gone. - var locationToReplace = testLspServer.GetLocations("caret").Single().Range; - await testLspServer.ReplaceTextAsync(csproj2Document.GetURI(), (locationToReplace, "B")); - var originalResultId = results.Single().ResultId; - results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics, originalResultId); - Assert.Single(results); - Assert.Empty(results.Single().Diagnostics); - Assert.NotEqual(originalResultId, results.Single().ResultId); - } + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); + var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); + var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); + + await testLspServer.OpenDocumentAsync(csproj1Document.GetURI()); + await testLspServer.OpenDocumentAsync(csproj2Document.GetURI()); + + // Verify we a diagnostic in A.cs since B does not exist. + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); + Assert.Single(results); + Assert.Equal("CS0246", results.Single().Diagnostics.Single().Code); + + // Insert B into B.cs and verify that the error in A.cs is now gone. + var locationToReplace = testLspServer.GetLocations("caret").Single().Range; + await testLspServer.ReplaceTextAsync(csproj2Document.GetURI(), (locationToReplace, "B")); + var originalResultId = results.Single().ResultId; + results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics, originalResultId); + Assert.Single(results); + Assert.Empty(results.Single().Diagnostics); + Assert.NotEqual(originalResultId, results.Single().ResultId); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsWithChangeInNotReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsWithChangeInNotReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"namespace M { class A : B { } }"; - var markup2 = + var markup2 = @"namespace M { public class {|caret:|} { } }"; - var workspaceXml = + var workspaceXml = @$" {markup1} @@ -603,171 +572,171 @@ public class {|caret:|} { } "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); - var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); - var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); - - await testLspServer.OpenDocumentAsync(csproj1Document.GetURI()); - await testLspServer.OpenDocumentAsync(csproj2Document.GetURI()); - - // Verify we get a diagnostic in A since the class B does not exist. - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); - Assert.Single(results); - Assert.Equal("CS0246", results.Single().Diagnostics.Single().Code); - - // Add B to CSProj2 and verify that we get an unchanged result (still has diagnostic) for A.cs - // since CSProj1 does not reference CSProj2 - var locationToReplace = testLspServer.GetLocations("caret").Single().Range; - await testLspServer.ReplaceTextAsync(csproj2Document.GetURI(), (locationToReplace, "B")); - var originalResultId = results.Single().ResultId; - results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics, originalResultId); - Assert.Single(results); - Assert.Null(results.Single().Diagnostics); - Assert.Equal(originalResultId, results.Single().ResultId); - } + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); + var csproj1Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj1").Single().Documents.First(); + var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); + + await testLspServer.OpenDocumentAsync(csproj1Document.GetURI()); + await testLspServer.OpenDocumentAsync(csproj2Document.GetURI()); + + // Verify we get a diagnostic in A since the class B does not exist. + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); + Assert.Single(results); + Assert.Equal("CS0246", results.Single().Diagnostics.Single().Code); + + // Add B to CSProj2 and verify that we get an unchanged result (still has diagnostic) for A.cs + // since CSProj1 does not reference CSProj2 + var locationToReplace = testLspServer.GetLocations("caret").Single().Range; + await testLspServer.ReplaceTextAsync(csproj2Document.GetURI(), (locationToReplace, "B")); + var originalResultId = results.Single().ResultId; + results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics, originalResultId); + Assert.Single(results); + Assert.Null(results.Single().Diagnostics); + Assert.Equal(originalResultId, results.Single().ResultId); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsFromRazorServer(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsFromRazorServer(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, - GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics, WellKnownLspServerKinds.RazorLspServer)); + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics, WellKnownLspServerKinds.RazorLspServer)); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); - // Assert that we have diagnostics even though the option is set to push. - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); - } + // Assert that we have diagnostics even though the option is set to push. + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsFromLiveShareServer(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsFromLiveShareServer(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A {"; - await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, - GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics, WellKnownLspServerKinds.LiveShareLspServer)); + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics, WellKnownLspServerKinds.LiveShareLspServer)); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); - // Assert that we have diagnostics even though the option is set to push. - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); - } + // Assert that we have diagnostics even though the option is set to push. + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsIncludesSourceGeneratorDiagnostics(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = "// Hello, World"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsIncludesSourceGeneratorDiagnostics(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = "// Hello, World"; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - var generator = new DiagnosticProducingGenerator(context => Location.Create(context.Compilation.SyntaxTrees.Single(), new TextSpan(0, 10))); + var generator = new DiagnosticProducingGenerator(context => Location.Create(context.Compilation.SyntaxTrees.Single(), new TextSpan(0, 10))); - testLspServer.TestWorkspace.OnAnalyzerReferenceAdded( - document.Project.Id, - new TestGeneratorReference(generator)); + testLspServer.TestWorkspace.OnAnalyzerReferenceAdded( + document.Project.Id, + new TestGeneratorReference(generator)); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); - var diagnostic = Assert.Single(results.Single().Diagnostics); - Assert.Equal(DiagnosticProducingGenerator.Descriptor.Id, diagnostic.Code); - } + var diagnostic = Assert.Single(results.Single().Diagnostics); + Assert.Equal(DiagnosticProducingGenerator.Descriptor.Id, diagnostic.Code); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsWithFadingOptionOn(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsWithFadingOptionOn(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @" {|first:using System.Linq; using System.Threading;|} class A { }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var firstLocation = testLspServer.GetLocations("first").Single().Range; - testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(FadingOptions.FadeOutUnusedImports, LanguageNames.CSharp, true); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + var firstLocation = testLspServer.GetLocations("first").Single().Range; + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(FadingOptions.FadeOutUnusedImports, LanguageNames.CSharp, true); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); - if (useVSDiagnostics) - { - // We should have an unnecessary diagnostic marking all the usings. - Assert.True(results.Single().Diagnostics![0].Tags!.Contains(DiagnosticTag.Unnecessary)); - Assert.Equal(firstLocation, results.Single().Diagnostics![1].Range); - - // We should have a regular diagnostic marking all the usings that doesn't fade. - Assert.False(results.Single().Diagnostics![1].Tags!.Contains(DiagnosticTag.Unnecessary)); - Assert.Equal(firstLocation, results.Single().Diagnostics![1].Range); - } - else - { - // We should have just one diagnostic that fades since the public spec does not support fully hidden diagnostics. - Assert.True(results.Single().Diagnostics![0].Tags!.Contains(DiagnosticTag.Unnecessary)); - Assert.Equal(firstLocation, results.Single().Diagnostics![0].Range); - } - } + if (useVSDiagnostics) + { + // We should have an unnecessary diagnostic marking all the usings. + Assert.True(results.Single().Diagnostics![0].Tags!.Contains(DiagnosticTag.Unnecessary)); + Assert.Equal(firstLocation, results.Single().Diagnostics![1].Range); - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsWithFadingOptionOff(bool useVSDiagnostics, bool mutatingLspWorkspace) + // We should have a regular diagnostic marking all the usings that doesn't fade. + Assert.False(results.Single().Diagnostics![1].Tags!.Contains(DiagnosticTag.Unnecessary)); + Assert.Equal(firstLocation, results.Single().Diagnostics![1].Range); + } + else { - var markup = + // We should have just one diagnostic that fades since the public spec does not support fully hidden diagnostics. + Assert.True(results.Single().Diagnostics![0].Tags!.Contains(DiagnosticTag.Unnecessary)); + Assert.Equal(firstLocation, results.Single().Diagnostics![0].Range); + } + } + + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsWithFadingOptionOff(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @" {|first:using System.Linq; using System.Threading;|} class A { }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var firstLocation = testLspServer.GetLocations("first").Single().Range; - testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(FadingOptions.FadeOutUnusedImports, LanguageNames.CSharp, false); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + var firstLocation = testLspServer.GetLocations("first").Single().Range; + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(FadingOptions.FadeOutUnusedImports, LanguageNames.CSharp, false); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); - Assert.All(results.Single().Diagnostics, d => Assert.False(d.Tags!.Contains(DiagnosticTag.Unnecessary))); - } + Assert.All(results.Single().Diagnostics, d => Assert.False(d.Tags!.Contains(DiagnosticTag.Unnecessary))); + } - [Theory, CombinatorialData] - public async Task TestDocumentDiagnosticsWithNotConfigurableFading(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestDocumentDiagnosticsWithNotConfigurableFading(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @"class A { void M() @@ -776,66 +745,66 @@ void M() 3 + 4{|close:)|}; } }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var openLocation = testLspServer.GetLocations("open").Single().Range; - var closeLocation = testLspServer.GetLocations("close").Single().Range; - var lineLocation = testLspServer.GetLocations("line").Single().Range; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + var openLocation = testLspServer.GetLocations("open").Single().Range; + var closeLocation = testLspServer.GetLocations("close").Single().Range; + var lineLocation = testLspServer.GetLocations("line").Single().Range; - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); - if (useVSDiagnostics) - { - // The first line should have a diagnostic on it that is not marked as unnecessary. - Assert.False(results.Single().Diagnostics![0].Tags!.Contains(DiagnosticTag.Unnecessary)); - Assert.Equal(lineLocation, results.Single().Diagnostics![0].Range); - - // The open paren should have an unnecessary diagnostic. - Assert.True(results.Single().Diagnostics![1].Tags!.Contains(DiagnosticTag.Unnecessary)); - Assert.Equal(openLocation, results.Single().Diagnostics![1].Range); - - // The close paren should have an unnecessary diagnostic. - Assert.True(results.Single().Diagnostics![2].Tags!.Contains(DiagnosticTag.Unnecessary)); - Assert.Equal(closeLocation, results.Single().Diagnostics![2].Range); - } - else - { - // There should be one unnecessary diagnostic. - Assert.True(results.Single().Diagnostics.Single().Tags!.Contains(DiagnosticTag.Unnecessary)); - Assert.Equal(lineLocation, results.Single().Diagnostics.Single().Range); + if (useVSDiagnostics) + { + // The first line should have a diagnostic on it that is not marked as unnecessary. + Assert.False(results.Single().Diagnostics![0].Tags!.Contains(DiagnosticTag.Unnecessary)); + Assert.Equal(lineLocation, results.Single().Diagnostics![0].Range); - // There should be an additional location for the open paren. - Assert.Equal(openLocation, results.Single().Diagnostics.Single().RelatedInformation![0].Location.Range); + // The open paren should have an unnecessary diagnostic. + Assert.True(results.Single().Diagnostics![1].Tags!.Contains(DiagnosticTag.Unnecessary)); + Assert.Equal(openLocation, results.Single().Diagnostics![1].Range); - // There should be an additional location for the close paren. - Assert.Equal(closeLocation, results.Single().Diagnostics.Single().RelatedInformation![1].Location.Range); - } + // The close paren should have an unnecessary diagnostic. + Assert.True(results.Single().Diagnostics![2].Tags!.Contains(DiagnosticTag.Unnecessary)); + Assert.Equal(closeLocation, results.Single().Diagnostics![2].Range); } - - [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1806590")] - public async Task TestDocumentDiagnosticsForUnnecessarySuppressions(bool useVSDiagnostics, bool mutatingLspWorkspace) + else { - var markup = "#pragma warning disable IDE0000"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + // There should be one unnecessary diagnostic. + Assert.True(results.Single().Diagnostics.Single().Tags!.Contains(DiagnosticTag.Unnecessary)); + Assert.Equal(lineLocation, results.Single().Diagnostics.Single().Range); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + // There should be an additional location for the open paren. + Assert.Equal(openLocation, results.Single().Diagnostics.Single().RelatedInformation![0].Location.Range); - await OpenDocumentAsync(testLspServer, document); + // There should be an additional location for the close paren. + Assert.Equal(closeLocation, results.Single().Diagnostics.Single().RelatedInformation![1].Location.Range); + } + } - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1806590")] + public async Task TestDocumentDiagnosticsForUnnecessarySuppressions(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = "#pragma warning disable IDE0000"; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - Assert.Equal(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, results.Single().Diagnostics.Single().Code); - } + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1824321")] - public async Task TestDocumentDiagnosticsForSourceSuppressions(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = @" + await OpenDocumentAsync(testLspServer, document); + + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); + + Assert.Equal(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, results.Single().Diagnostics.Single().Code); + } + + [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1824321")] + public async Task TestDocumentDiagnosticsForSourceSuppressions(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = @" class C { void M() @@ -845,418 +814,497 @@ void M() #pragma warning restore CS0168 // Variable is declared but never used } }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Empty(results.Single().Diagnostics); - } + Assert.Empty(results.Single().Diagnostics); + } - [Theory, CombinatorialData] - public async Task TestInfoDiagnosticsAreReportedAsInformationInVS(bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestInfoDiagnosticsAreReportedAsInformationInVS(bool mutatingLspWorkspace) + { + var markup = @"class A { public A SomeA = new A(); }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics: true); - Assert.Equal("IDE0090", results.Single().Diagnostics.Single().Code); - Assert.Equal(LSP.DiagnosticSeverity.Information, results.Single().Diagnostics.Single().Severity); - } + Assert.Equal("IDE0090", results.Single().Diagnostics.Single().Code); + Assert.Equal(LSP.DiagnosticSeverity.Information, results.Single().Diagnostics.Single().Severity); + } - [Theory, CombinatorialData] - public async Task TestInfoDiagnosticsAreReportedAsHintInVSCode(bool mutatingLspWorkspace) - { - var markup = + [Theory, CombinatorialData] + public async Task TestInfoDiagnosticsAreReportedAsHintInVSCode(bool mutatingLspWorkspace) + { + var markup = @"class A { public A SomeA = new A(); }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: false); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: false); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - await OpenDocumentAsync(testLspServer, document); + await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: false); + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics: false); - Assert.Equal("IDE0090", results.Single().Diagnostics.Single().Code); - Assert.Equal(LSP.DiagnosticSeverity.Hint, results.Single().Diagnostics.Single().Severity); - } + Assert.Equal("IDE0090", results.Single().Diagnostics.Single().Code); + Assert.Equal(LSP.DiagnosticSeverity.Hint, results.Single().Diagnostics.Single().Severity); + } - #endregion + #endregion - #region Workspace Diagnostics + #region Workspace Diagnostics - [Theory, CombinatorialData] - public async Task TestNoWorkspaceDiagnosticsForClosedFilesWithFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestNoWorkspaceDiagnosticsForClosedFilesWithFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Empty(results); - } + Assert.Empty(results); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsForClosedFilesWithFSAOn(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsForClosedFilesWithFSAOn(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Empty(results[1].Diagnostics); - Assert.Empty(results[2].Diagnostics); - } + Assert.Equal(3, results.Length); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Empty(results[1].Diagnostics); + Assert.Empty(results[2].Diagnostics); + } - [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/65967")] - public async Task TestWorkspaceDiagnosticsForClosedFilesWithRunCodeAnalysisAndFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace, bool scopeRunCodeAnalysisToProject) - { - var markup1 = + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/65967")] + public async Task TestWorkspaceDiagnosticsForClosedFilesWithRunCodeAnalysisAndFSAOff(bool useVSDiagnostics, bool mutatingLspWorkspace, bool scopeRunCodeAnalysisToProject) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var projectId = scopeRunCodeAnalysisToProject ? testLspServer.GetCurrentSolution().Projects.Single().Id : null; - await testLspServer.RunCodeAnalysisAsync(projectId); + var projectId = scopeRunCodeAnalysisToProject ? testLspServer.GetCurrentSolution().Projects.Single().Id : null; + await testLspServer.RunCodeAnalysisAsync(projectId); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - // this should be considered a build-error, since it was produced by the last code-analysis run. - Assert.Contains(VSDiagnosticTags.BuildError, results[0].Diagnostics.Single().Tags); - Assert.Empty(results[1].Diagnostics); - Assert.Empty(results[2].Diagnostics); + Assert.Equal(3, results.Length); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + // this should be considered a build-error, since it was produced by the last code-analysis run. + Assert.Contains(VSDiagnosticTags.BuildError, results[0].Diagnostics.Single().Tags); + Assert.Empty(results[1].Diagnostics); + Assert.Empty(results[2].Diagnostics); - // Now fix the compiler error, but don't re-execute code analysis. - // Verify that we still get the workspace diagnostics from the prior snapshot on which code analysis was executed. - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "}"); + // Now fix the compiler error, but don't re-execute code analysis. + // Verify that we still get the workspace diagnostics from the prior snapshot on which code analysis was executed. + var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); + buffer.Insert(buffer.CurrentSnapshot.Length, "}"); - var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); + var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); - Assert.Equal(results.Length, results2.Length); + Assert.Equal(results.Length, results2.Length); - Assert.Equal(results[0].Diagnostics, results2[0].Diagnostics); - // this should be considered a build-error, since it was produced by the last code-analysis run. - Assert.Contains(VSDiagnosticTags.BuildError, results2[0].Diagnostics.Single().Tags); - Assert.Equal(results[1].Diagnostics, results2[1].Diagnostics); - Assert.Equal(results[2].Diagnostics, results2[2].Diagnostics); + Assert.Equal(results[0].Diagnostics, results2[0].Diagnostics); + // this should be considered a build-error, since it was produced by the last code-analysis run. + Assert.Contains(VSDiagnosticTags.BuildError, results2[0].Diagnostics.Single().Tags); + Assert.Equal(results[1].Diagnostics, results2[1].Diagnostics); + Assert.Equal(results[2].Diagnostics, results2[2].Diagnostics); - // Re-run code analysis and verify up-to-date diagnostics are returned now, i.e. there are no compiler errors. - await testLspServer.RunCodeAnalysisAsync(projectId); + // Re-run code analysis and verify up-to-date diagnostics are returned now, i.e. there are no compiler errors. + await testLspServer.RunCodeAnalysisAsync(projectId); - var results3 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results2)); + var results3 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results2)); - Assert.Equal(results.Length, results3.Length); - Assert.Empty(results3[0].Diagnostics); - Assert.Empty(results3[1].Diagnostics); - Assert.Empty(results3[2].Diagnostics); - } + Assert.Equal(results.Length, results3.Length); + Assert.Empty(results3[0].Diagnostics); + Assert.Empty(results3[1].Diagnostics); + Assert.Empty(results3[2].Diagnostics); + } - [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/65967")] - public async Task TestWorkspaceDiagnosticsForClosedFilesWithWithRunCodeAnalysisFSAOn(bool useVSDiagnostics, bool mutatingLspWorkspace, bool scopeRunCodeAnalysisToProject) - { - var markup1 = + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/65967")] + public async Task TestWorkspaceDiagnosticsForClosedFilesWithWithRunCodeAnalysisFSAOn(bool useVSDiagnostics, bool mutatingLspWorkspace, bool scopeRunCodeAnalysisToProject) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - // Run code analysis on the initial project snapshot with compiler error. - var projectId = scopeRunCodeAnalysisToProject ? testLspServer.GetCurrentSolution().Projects.Single().Id : null; - await testLspServer.RunCodeAnalysisAsync(projectId); + // Run code analysis on the initial project snapshot with compiler error. + var projectId = scopeRunCodeAnalysisToProject ? testLspServer.GetCurrentSolution().Projects.Single().Id : null; + await testLspServer.RunCodeAnalysisAsync(projectId); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - // this should *not* be considered a build-error, since it was produced by the live workspace results. - Assert.DoesNotContain(VSDiagnosticTags.BuildError, results[0].Diagnostics.Single().Tags); - Assert.Empty(results[1].Diagnostics); - Assert.Empty(results[2].Diagnostics); + Assert.Equal(3, results.Length); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + // this should *not* be considered a build-error, since it was produced by the live workspace results. + Assert.DoesNotContain(VSDiagnosticTags.BuildError, results[0].Diagnostics.Single().Tags); + Assert.Empty(results[1].Diagnostics); + Assert.Empty(results[2].Diagnostics); - // Now fix the compiler error, but don't rerun code analysis. - // Verify that we get up-to-date workspace diagnostics, i.e. no compiler errors, from the current snapshot because FSA is enabled. - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "}"); + // Now fix the compiler error, but don't rerun code analysis. + // Verify that we get up-to-date workspace diagnostics, i.e. no compiler errors, from the current snapshot because FSA is enabled. + var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); + buffer.Insert(buffer.CurrentSnapshot.Length, "}"); - var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); + var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); - Assert.Equal(results.Length, results2.Length); + Assert.Equal(results.Length, results2.Length); - Assert.Equal(results.Length, results2.Length); - Assert.Empty(results2[0].Diagnostics); - Assert.Empty(results2[1].Diagnostics); - Assert.Empty(results2[2].Diagnostics); + Assert.Equal(results.Length, results2.Length); + Assert.Empty(results2[0].Diagnostics); + Assert.Empty(results2[1].Diagnostics); + Assert.Empty(results2[2].Diagnostics); - // Now rerun code analysis and verify we still get up-to-date workspace diagnostics. - await testLspServer.RunCodeAnalysisAsync(projectId); + // Now rerun code analysis and verify we still get up-to-date workspace diagnostics. + await testLspServer.RunCodeAnalysisAsync(projectId); - var results3 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results2)); + var results3 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results2)); - Assert.Equal(results2.Length, results3.Length); + Assert.Equal(results2.Length, results3.Length); - Assert.Equal(results2[0].Diagnostics, results3[0].Diagnostics); - Assert.Equal(results2[1].Diagnostics, results3[1].Diagnostics); - Assert.Equal(results2[2].Diagnostics, results3[2].Diagnostics); - } + Assert.Equal(results2[0].Diagnostics, results3[0].Diagnostics); + Assert.Equal(results2[1].Diagnostics, results3[1].Diagnostics); + Assert.Equal(results2[2].Diagnostics, results3[2].Diagnostics); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOff(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOff(bool mutatingLspWorkspace) + { + var markup1 = @" // todo: goo class A { }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1 }, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: false, category: PullDiagnosticCategories.Task); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: false, category: PullDiagnosticCategories.Task); - Assert.Equal(0, results.Length); - } + Assert.Equal(0, results.Length); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn(bool mutatingLspWorkspace) + { + var markup1 = @" // todo: goo class A { }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1 }, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: true, category: PullDiagnosticCategories.Task); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - if (useVSDiagnostics) - { - Assert.Equal(1, results.Length); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); - Assert.Equal(VSDiagnosticRank.Default, ((VSDiagnostic)results[0].Diagnostics.Single()).DiagnosticRank); - } - else - { - Assert.Empty(results); - } - } + Assert.Equal(1, results.Length); + Assert.Equal("TODO", results[0].Diagnostics.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); + Assert.Equal(VSDiagnosticRank.Default, ((VSDiagnostic)results[0].Diagnostics.Single()).DiagnosticRank); + } - [Theory] - [InlineData("1", (int)VSDiagnosticRank.Low, false)] - [InlineData("1", (int)VSDiagnosticRank.Low, true)] - [InlineData("2", (int)VSDiagnosticRank.Default, false)] - [InlineData("2", (int)VSDiagnosticRank.Default, true)] - [InlineData("3", (int)VSDiagnosticRank.High, false)] - [InlineData("3", (int)VSDiagnosticRank.High, true)] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn_Priorities( - string priString, int intRank, bool mutatingLspWorkspace) - { - var rank = (VSDiagnosticRank)intRank; - var markup1 = + [Theory] + [InlineData("1", (int)VSDiagnosticRank.Low, false)] + [InlineData("1", (int)VSDiagnosticRank.Low, true)] + [InlineData("2", (int)VSDiagnosticRank.Default, false)] + [InlineData("2", (int)VSDiagnosticRank.Default, true)] + [InlineData("3", (int)VSDiagnosticRank.High, false)] + [InlineData("3", (int)VSDiagnosticRank.High, true)] + public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn_Priorities( + string priString, int intRank, bool mutatingLspWorkspace) + { + var rank = (VSDiagnosticRank)intRank; + var markup1 = @" // todo: goo class A { }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1 }, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption( - TaskListOptionsStorage.Descriptors, - ImmutableArray.Create("HACK:2", $"TODO:{priString}", "UNDONE:2", "UnresolvedMergeConflict:3")); + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption( + TaskListOptionsStorage.Descriptors, + ImmutableArray.Create("HACK:2", $"TODO:{priString}", "UNDONE:2", "UnresolvedMergeConflict:3")); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - Assert.Equal(1, results.Length); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); - Assert.Equal(rank, ((VSDiagnostic)results[0].Diagnostics.Single()).DiagnosticRank); - } + Assert.Equal(1, results.Length); + Assert.Equal("TODO", results[0].Diagnostics.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); + Assert.Equal(rank, ((VSDiagnostic)results[0].Diagnostics.Single()).DiagnosticRank); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOff(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOff(bool mutatingLspWorkspace) + { + var markup1 = @" // todo: goo class A { }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: false, category: PullDiagnosticCategories.Task); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: false, category: PullDiagnosticCategories.Task); - if (useVSDiagnostics) - { - Assert.Equal(0, results.Length); - } - else - { - Assert.Equal(2, results.Length); - Assert.Empty(results[0].Diagnostics); - Assert.Empty(results[1].Diagnostics); - } - } + Assert.Equal(0, results.Length); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOn(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOn(bool mutatingLspWorkspace) + { + var markup1 = @" // todo: goo class A { }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: true, category: PullDiagnosticCategories.Task); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - if (useVSDiagnostics) - { - Assert.Equal(1, results.Length); + Assert.Equal(1, results.Length); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); - } - else - { - Assert.Equal(2, results.Length); - - Assert.Empty(results[0].Diagnostics); - Assert.Empty(results[1].Diagnostics); - } - } + Assert.Equal("TODO", results[0].Diagnostics.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceTodoAndDiagnosticForClosedFilesWithFSAOnAndTodoOn(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceTodoAndDiagnosticForClosedFilesWithFSAOnAndTodoOn(bool mutatingLspWorkspace) + { + var markup1 = @" // todo: goo class A { "; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: true, category: PullDiagnosticCategories.Task); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - if (useVSDiagnostics) - { - Assert.Equal(1, results.Length); + Assert.Equal(1, results.Length); + Assert.Equal("TODO", results[0].Diagnostics![0].Code); + } - Assert.Equal("TODO", results[0].Diagnostics![0].Code); - } - else - { - Assert.Equal(2, results.Length); + [Theory, CombinatorialData] + public async Task EditAndContinue_NoActiveSession(bool mutatingLspWorkspace) + { + var markup1 = "class C {}"; - Assert.Equal("CS1513", results[0].Diagnostics![0].Code); + var options = GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, compilerDiagnosticsScope: null, useVSDiagnostics: false); - Assert.Empty(results[1].Diagnostics); - } - } + await using var testLspServer = await CreateTestLspServerAsync([markup1], LanguageNames.CSharp, mutatingLspWorkspace, options); - [Theory, CombinatorialData] - public async Task TestNoWorkspaceDiagnosticsForClosedFilesWithFSAOffWithFileInProjectOpen(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + var encSessionState = testLspServer.TestWorkspace.GetService(); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: false, includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); + Assert.Empty(results); + } + + [Theory, CombinatorialData] + public async Task EditAndContinue(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var options = GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, compilerDiagnosticsScope: null, useVSDiagnostics); + var composition = Composition + .AddExcludedPartTypes(typeof(EditAndContinueService)) + .AddParts(typeof(MockEditAndContinueService)); + + await using var testLspServer = await CreateTestLspServerAsync(["class C;", "class D;"], LanguageNames.CSharp, mutatingLspWorkspace, options, composition); + + var encSessionState = testLspServer.TestWorkspace.GetService(); + var encService = (MockEditAndContinueService)testLspServer.TestWorkspace.GetService(); + var diagnosticsRefresher = testLspServer.TestWorkspace.GetService(); + + var project = testLspServer.TestWorkspace.CurrentSolution.Projects.Single(); + var openDocument = project.Documents.First(); + var closedDocument = project.Documents.Skip(1).First(); + + await OpenDocumentAsync(testLspServer, openDocument); + + var projectDiagnostic = CreateDiagnostic("ENC_PROJECT", project: project); + var openDocumentDiagnostic1 = CreateDiagnostic("ENC_OPEN_DOC1", openDocument); + var openDocumentDiagnostic2 = await CreateDiagnostic("ENC_OPEN_DOC2", openDocument).ToDiagnosticAsync(project, CancellationToken.None); + var closedDocumentDiagnostic = CreateDiagnostic("ENC_CLOSED_DOC", closedDocument); + + encSessionState.IsSessionActive = true; + encSessionState.ApplyChangesDiagnostics = [projectDiagnostic, openDocumentDiagnostic1, closedDocumentDiagnostic]; + encService.GetDocumentDiagnosticsImpl = (_, _) => [openDocumentDiagnostic2]; + + var documentResults1 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, openDocument.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.EditAndContinue); + + // both diagnostics located in the open document are reported: + AssertEx.Equal( + [ + "file:///C:/test1.cs -> [ENC_OPEN_DOC1,ENC_OPEN_DOC2]", + ], documentResults1.Select(Inspect)); + + var workspaceResults1 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); + + AssertEx.Equal( + [ + "file:///C:/test2.cs -> [ENC_CLOSED_DOC]", + "file:///C:/Test.csproj -> [ENC_PROJECT]", + ], workspaceResults1.Select(Inspect)); + + // clear workspace diagnostics: + + encSessionState.ApplyChangesDiagnostics = []; + diagnosticsRefresher.RequestWorkspaceRefresh(); + + var documentResults2 = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, openDocument.GetURI(), previousResultId: documentResults1.Single().ResultId, useVSDiagnostics: useVSDiagnostics, category: PullDiagnosticCategories.EditAndContinue); + + AssertEx.Equal( + [ + "file:///C:/test1.cs -> [ENC_OPEN_DOC2]", + ], documentResults2.Select(Inspect)); + + var workspaceResults2 = await RunGetWorkspacePullDiagnosticsAsync( + testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(workspaceResults1), includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); + AssertEx.Equal( + [ + "file:///C:/test2.cs -> []", + "file:///C:/Test.csproj -> []", + ], workspaceResults2.Select(Inspect)); + + // deactivate EnC session: + + encSessionState.IsSessionActive = false; + diagnosticsRefresher.RequestWorkspaceRefresh(); + + var documentResults3 = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, openDocument.GetURI(), previousResultId: documentResults2.Single().ResultId, useVSDiagnostics: useVSDiagnostics, category: PullDiagnosticCategories.EditAndContinue); + AssertEx.Equal( + [ + "file:///C:/test1.cs -> []", + ], documentResults3.Select(Inspect)); + + var workspaceResults3 = await RunGetWorkspacePullDiagnosticsAsync( + testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(workspaceResults2), includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); + AssertEx.Equal([], workspaceResults3.Select(Inspect)); + + static DiagnosticData CreateDiagnostic(string id, Document? document = null, Project? project = null) + => new( + id, + category: "EditAndContinue", + message: "test message", + severity: DiagnosticSeverity.Error, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + warningLevel: 0, + projectId: project?.Id, + customTags: [], + properties: ImmutableDictionary.Empty, + location: new DiagnosticDataLocation(new FileLinePositionSpan("file", span: default), document?.Id), + additionalLocations: [], + language: (project ?? document!.Project).Language); + + static string Inspect(TestDiagnosticResult result) + => $"{result.TextDocument.Uri} -> [{string.Join(",", result.Diagnostics?.Select(d => d.Code?.Value) ?? [])}]"; + } + + [Theory, CombinatorialData] + public async Task TestNoWorkspaceDiagnosticsForClosedFilesWithFSAOffWithFileInProjectOpen(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var firstDocument = testLspServer.GetCurrentSolution().Projects.Single().Documents.First(); - await OpenDocumentAsync(testLspServer, firstDocument); + var firstDocument = testLspServer.GetCurrentSolution().Projects.Single().Documents.First(); + await OpenDocumentAsync(testLspServer, firstDocument); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Empty(results); - } + Assert.Empty(results); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsIncludesSourceGeneratorDiagnosticsClosedFSAOn(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = "// Hello, World"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - markup, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsIncludesSourceGeneratorDiagnosticsClosedFSAOn(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = "// Hello, World"; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + markup, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); - var generator = new DiagnosticProducingGenerator(context => Location.Create(context.Compilation.SyntaxTrees.Single(), new TextSpan(0, 10))); + var generator = new DiagnosticProducingGenerator(context => Location.Create(context.Compilation.SyntaxTrees.Single(), new TextSpan(0, 10))); - testLspServer.TestWorkspace.OnAnalyzerReferenceAdded( - document.Project.Id, - new TestGeneratorReference(generator)); + testLspServer.TestWorkspace.OnAnalyzerReferenceAdded( + document.Project.Id, + new TestGeneratorReference(generator)); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(DiagnosticProducingGenerator.Descriptor.Id, results[0].Diagnostics.Single().Code); - Assert.Empty(results[1].Diagnostics); - } + Assert.Equal(DiagnosticProducingGenerator.Descriptor.Id, results[0].Diagnostics.Single().Code); + Assert.Empty(results[1].Diagnostics); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsDoesNotIncludeSourceGeneratorDiagnosticsClosedFSAOffAndNoFilesOpen(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup = "// Hello, World"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsDoesNotIncludeSourceGeneratorDiagnosticsClosedFSAOffAndNoFilesOpen(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup = "// Hello, World"; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - var generator = new DiagnosticProducingGenerator( - context => Location.Create( - context.Compilation.SyntaxTrees.Single(), - new TextSpan(0, 10))); + var generator = new DiagnosticProducingGenerator( + context => Location.Create( + context.Compilation.SyntaxTrees.Single(), + new TextSpan(0, 10))); - testLspServer.TestWorkspace.OnAnalyzerReferenceAdded( - testLspServer.GetCurrentSolution().Projects.Single().Id, - new TestGeneratorReference(generator)); + testLspServer.TestWorkspace.OnAnalyzerReferenceAdded( + testLspServer.GetCurrentSolution().Projects.Single().Id, + new TestGeneratorReference(generator)); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Empty(results); - } + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + Assert.Empty(results); + } - [Theory, CombinatorialData] - public async Task TestNoWorkspaceDiagnosticsForClosedFilesInProjectsWithIncorrectLanguage(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var csharpMarkup = + [Theory, CombinatorialData] + public async Task TestNoWorkspaceDiagnosticsForClosedFilesInProjectsWithIncorrectLanguage(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var csharpMarkup = @"class A {"; - var typeScriptMarkup = "???"; + var typeScriptMarkup = "???"; - var workspaceXml = + var workspaceXml = @$" {csharpMarkup} @@ -1266,218 +1314,219 @@ public async Task TestNoWorkspaceDiagnosticsForClosedFilesInProjectsWithIncorrec "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.False(results.Any(r => r.TextDocument!.Uri.LocalPath.Contains(".ts"))); - } + Assert.False(results.Any(r => r.TextDocument!.Uri.LocalPath.Contains(".ts"))); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsForSourceGeneratedFiles(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsForSourceGeneratedFiles(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestLspServerAsync( - markups: [], mutatingLspWorkspace, - GetInitializationOptions(BackgroundAnalysisScope.FullSolution, CompilerDiagnosticsScope.FullSolution, useVSDiagnostics, sourceGeneratedMarkups: [markup1, markup2])); - - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - - // Project.GetSourceGeneratedDocumentsAsync may not return documents in a deterministic order, so we sort - // the results here to ensure subsequent assertions are not dependent on the order of items provided by the - // project. - results = results.Sort((x, y) => x.Uri.ToString().CompareTo(y.Uri.ToString())); - - Assert.Equal(3, results.Length); - // Since we sorted above by URI the first result is the project. - Assert.Empty(results[0].Diagnostics); - Assert.Equal("CS1513", results[1].Diagnostics.Single().Code); - Assert.Empty(results[2].Diagnostics); - } + var markup2 = ""; + await using var testLspServer = await CreateTestLspServerAsync( + markups: [], mutatingLspWorkspace, + GetInitializationOptions(BackgroundAnalysisScope.FullSolution, CompilerDiagnosticsScope.FullSolution, useVSDiagnostics, sourceGeneratedMarkups: [markup1, markup2])); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + + // Project.GetSourceGeneratedDocumentsAsync may not return documents in a deterministic order, so we sort + // the results here to ensure subsequent assertions are not dependent on the order of items provided by the + // project. + results = results.Sort((x, y) => x.Uri.ToString().CompareTo(y.Uri.ToString())); + + Assert.Equal(3, results.Length); + // Since we sorted above by URI the first result is the project. + Assert.Empty(results[0].Diagnostics); + Assert.Equal("CS1513", results[1].Diagnostics.Single().Code); + Assert.Empty(results[2].Diagnostics); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsForRemovedDocument(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsForRemovedDocument(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Empty(results[1].Diagnostics); - Assert.Empty(results[2].Diagnostics); + Assert.Equal(3, results.Length); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Empty(results[1].Diagnostics); + Assert.Empty(results[2].Diagnostics); - testLspServer.TestWorkspace.OnDocumentRemoved(testLspServer.TestWorkspace.Documents.First().Id); + testLspServer.TestWorkspace.OnDocumentRemoved(testLspServer.TestWorkspace.Documents.First().Id); - var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); + var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); - // First doc should show up as removed. - Assert.Equal(3, results2.Length); - Assert.Equal(useVSDiagnostics ? null : [], results2[0].Diagnostics); - Assert.Null(results2[0].ResultId); + // First doc should show up as removed. + Assert.Equal(3, results2.Length); + // VS represents removal with null diagnostics, VS code represents with an empty diagnostics array. + Assert.Equal(useVSDiagnostics ? null : [], results2[0].Diagnostics); + Assert.Null(results2[0].ResultId); - // Second and third doc should be changed as the project has changed. - Assert.Empty(results2[1].Diagnostics); - Assert.NotEqual(results[1].ResultId, results2[1].ResultId); - Assert.Empty(results2[2].Diagnostics); - Assert.NotEqual(results[2].ResultId, results2[2].ResultId); - } + // Second and third doc should be changed as the project has changed. + Assert.Empty(results2[1].Diagnostics); + Assert.NotEqual(results[1].ResultId, results2[1].ResultId); + Assert.Empty(results2[2].Diagnostics); + Assert.NotEqual(results[2].ResultId, results2[2].ResultId); + } - [Theory, CombinatorialData] - public async Task TestNoChangeIfWorkspaceDiagnosticsCalledTwice(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestNoChangeIfWorkspaceDiagnosticsCalledTwice(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Empty(results[1].Diagnostics); - Assert.Empty(results[2].Diagnostics); + Assert.Equal(3, results.Length); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Empty(results[1].Diagnostics); + Assert.Empty(results[2].Diagnostics); - var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); + var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); - // 'no changes' will be reported as an empty array. - Assert.Empty(results2); - } + // 'no changes' will be reported as an empty array. + Assert.Empty(results2); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsRemovedAfterErrorIsFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsRemovedAfterErrorIsFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Empty(results[1].Diagnostics); - Assert.Empty(results[2].Diagnostics); + Assert.Equal(3, results.Length); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Empty(results[1].Diagnostics); + Assert.Empty(results[2].Diagnostics); - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "}"); + var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); + buffer.Insert(buffer.CurrentSnapshot.Length, "}"); - var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); + var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); - Assert.Equal(3, results2.Length); - Assert.Empty(results2[0].Diagnostics); - // Project has changed, so we re-computed diagnostics as changes in the first file - // may have changed results in the second. - Assert.Empty(results2[1].Diagnostics); - Assert.Empty(results2[2].Diagnostics); + Assert.Equal(3, results2.Length); + Assert.Empty(results2[0].Diagnostics); + // Project has changed, so we re-computed diagnostics as changes in the first file + // may have changed results in the second. + Assert.Empty(results2[1].Diagnostics); + Assert.Empty(results2[2].Diagnostics); - Assert.NotEqual(results[0].ResultId, results2[0].ResultId); - Assert.NotEqual(results[1].ResultId, results2[1].ResultId); - Assert.NotEqual(results[2].ResultId, results2[2].ResultId); - } + Assert.NotEqual(results[0].ResultId, results2[0].ResultId); + Assert.NotEqual(results[1].ResultId, results2[1].ResultId); + Assert.NotEqual(results[2].ResultId, results2[2].ResultId); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsRemainAfterErrorIsNotFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsRemainAfterErrorIsNotFixed(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); + Assert.Equal(3, results.Length); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); - Assert.Empty(results[1].Diagnostics); - Assert.Empty(results[2].Diagnostics); + Assert.Empty(results[1].Diagnostics); + Assert.Empty(results[2].Diagnostics); - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(0, " "); + var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); + buffer.Insert(0, " "); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.First(); - var text = await document.GetTextAsync(); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.First(); + var text = await document.GetTextAsync(); - // Hacky, but we need to close the document manually since editing the text-buffer will open it in the - // test-workspace. - testLspServer.TestWorkspace.OnDocumentClosed( - document.Id, TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create()))); + // Hacky, but we need to close the document manually since editing the text-buffer will open it in the + // test-workspace. + testLspServer.TestWorkspace.OnDocumentClosed( + document.Id, TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create()))); - var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal("CS1513", results2[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 10 }, results2[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results2[0].Diagnostics.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 10 }, results2[0].Diagnostics.Single().Range.Start); - Assert.Empty(results2[1].Diagnostics); - Assert.NotEqual(results[1].ResultId, results2[1].ResultId); - Assert.Empty(results2[2].Diagnostics); - Assert.NotEqual(results[2].ResultId, results2[2].ResultId); - } + Assert.Empty(results2[1].Diagnostics); + Assert.NotEqual(results[1].ResultId, results2[1].ResultId); + Assert.Empty(results2[2].Diagnostics); + Assert.NotEqual(results[2].ResultId, results2[2].ResultId); + } - [Theory, CombinatorialData] - public async Task TestStreamingWorkspaceDiagnostics(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestStreamingWorkspaceDiagnostics(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); + Assert.Equal(3, results.Length); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); - results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, useProgress: true); + results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, useProgress: true); - Assert.Equal("CS1513", results[0].Diagnostics![0].Code); - } + Assert.Equal("CS1513", results[0].Diagnostics![0].Code); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsAreNotMapped(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsAreNotMapped(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"#line 1 ""test.txt"" class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(3, results.Length); - Assert.Equal(ProtocolConversions.CreateAbsoluteUri(@"C:\test1.cs"), results[0].TextDocument!.Uri); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(1, results[0].Diagnostics.Single().Range.Start.Line); - Assert.Empty(results[1].Diagnostics); - Assert.Empty(results[2].Diagnostics); - } + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + Assert.Equal(3, results.Length); + Assert.Equal(ProtocolConversions.CreateAbsoluteUri(@"C:\test1.cs"), results[0].TextDocument!.Uri); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal(1, results[0].Diagnostics.Single().Range.Start.Line); + Assert.Empty(results[1].Diagnostics); + Assert.Empty(results[2].Diagnostics); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsWithChangeInReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsWithChangeInReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"namespace M { class A : B { } }"; - var markup2 = + var markup2 = @"namespace M { public class {|caret:|} { } }"; - var workspaceXml = + var workspaceXml = @$" {markup1} @@ -1488,56 +1537,56 @@ public class {|caret:|} { } "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); - var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); - - // Verify we a diagnostic in A.cs since B does not exist - // and a diagnostic in B.cs since it is missing the class name. - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - AssertEx.NotNull(results); - Assert.Equal(4, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); - - // Insert B into B.cs via the workspace. - var caretLocation = testLspServer.GetLocations("caret").First().Range; - var csproj2DocumentText = await csproj2Document.GetTextAsync(); - var newCsProj2Document = csproj2Document.WithText(csproj2DocumentText.WithChanges(new TextChange(ProtocolConversions.RangeToTextSpan(caretLocation, csproj2DocumentText), "B"))); - await testLspServer.TestWorkspace.ChangeDocumentAsync(csproj2Document.Id, newCsProj2Document.Project.Solution); - - // Get updated workspace diagnostics for the change. - var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); - results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResultIds); - AssertEx.NotNull(results); - Assert.Equal(4, results.Length); - - // Verify diagnostics for A.cs are updated as the type B now exists. - Assert.Empty(results[0].Diagnostics); - Assert.NotEqual(previousResultIds[0].resultId, results[0].ResultId); - - // Verify diagnostics for B.cs are updated as the class definition is now correct. - Assert.Empty(results[2].Diagnostics); - Assert.NotEqual(previousResultIds[2].resultId, results[2].ResultId); - } + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); + var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); + + // Verify we a diagnostic in A.cs since B does not exist + // and a diagnostic in B.cs since it is missing the class name. + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + AssertEx.NotNull(results); + Assert.Equal(4, results.Length); + Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + + // Insert B into B.cs via the workspace. + var caretLocation = testLspServer.GetLocations("caret").First().Range; + var csproj2DocumentText = await csproj2Document.GetTextAsync(); + var newCsProj2Document = csproj2Document.WithText(csproj2DocumentText.WithChanges(new TextChange(ProtocolConversions.RangeToTextSpan(caretLocation, csproj2DocumentText), "B"))); + await testLspServer.TestWorkspace.ChangeDocumentAsync(csproj2Document.Id, newCsProj2Document.Project.Solution); + + // Get updated workspace diagnostics for the change. + var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); + results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResultIds); + AssertEx.NotNull(results); + Assert.Equal(4, results.Length); + + // Verify diagnostics for A.cs are updated as the type B now exists. + Assert.Empty(results[0].Diagnostics); + Assert.NotEqual(previousResultIds[0].resultId, results[0].ResultId); + + // Verify diagnostics for B.cs are updated as the class definition is now correct. + Assert.Empty(results[2].Diagnostics); + Assert.NotEqual(previousResultIds[2].resultId, results[2].ResultId); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsWithChangeInRecursiveReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsWithChangeInRecursiveReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"namespace M { public class A { } }"; - var markup2 = + var markup2 = @"namespace M { public class B { } }"; - var markup3 = + var markup3 = @"namespace M { public class {|caret:|} @@ -1545,7 +1594,7 @@ public class {|caret:|} } }"; - var workspaceXml = + var workspaceXml = @$" CSProj2 @@ -1560,65 +1609,65 @@ public class {|caret:|} "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); - var csproj3Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj3").Single().Documents.First(); - - // Verify we have a diagnostic in C.cs initially. - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - AssertEx.NotNull(results); - Assert.Equal(6, results.Length); - Assert.Empty(results[0].Diagnostics); - Assert.Empty(results[1].Diagnostics); - Assert.Empty(results[2].Diagnostics); - Assert.Empty(results[3].Diagnostics); - Assert.Equal("CS1001", results[4].Diagnostics.Single().Code); - Assert.Empty(results[5].Diagnostics); - - // Insert C into C.cs via the workspace. - var caretLocation = testLspServer.GetLocations("caret").First().Range; - var csproj3DocumentText = await csproj3Document.GetTextAsync().ConfigureAwait(false); - var newCsProj3Document = csproj3Document.WithText(csproj3DocumentText.WithChanges(new TextChange(ProtocolConversions.RangeToTextSpan(caretLocation, csproj3DocumentText), "C"))); - await testLspServer.TestWorkspace.ChangeDocumentAsync(csproj3Document.Id, newCsProj3Document.Project.Solution).ConfigureAwait(false); - - // Get updated workspace diagnostics for the change. - var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); - results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResultIds).ConfigureAwait(false); - AssertEx.NotNull(results); - Assert.Equal(6, results.Length); - - // Verify that new diagnostics are returned for all files (even though the diagnostics for the first two files are the same) - // since we re-calculate when transitive project dependencies change. - Assert.Empty(results[0].Diagnostics); - Assert.NotEqual(previousResultIds[0].resultId, results[0].ResultId); - Assert.Empty(results[1].Diagnostics); - Assert.NotEqual(previousResultIds[1].resultId, results[1].ResultId); - - Assert.Empty(results[2].Diagnostics); - Assert.NotEqual(previousResultIds[2].resultId, results[2].ResultId); - Assert.Empty(results[3].Diagnostics); - Assert.NotEqual(previousResultIds[3].resultId, results[3].ResultId); - - Assert.Empty(results[4].Diagnostics); - Assert.NotEqual(previousResultIds[4].resultId, results[4].ResultId); - Assert.Empty(results[5].Diagnostics); - Assert.NotEqual(previousResultIds[5].resultId, results[5].ResultId); - } + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); + var csproj3Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj3").Single().Documents.First(); + + // Verify we have a diagnostic in C.cs initially. + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + AssertEx.NotNull(results); + Assert.Equal(6, results.Length); + Assert.Empty(results[0].Diagnostics); + Assert.Empty(results[1].Diagnostics); + Assert.Empty(results[2].Diagnostics); + Assert.Empty(results[3].Diagnostics); + Assert.Equal("CS1001", results[4].Diagnostics.Single().Code); + Assert.Empty(results[5].Diagnostics); + + // Insert C into C.cs via the workspace. + var caretLocation = testLspServer.GetLocations("caret").First().Range; + var csproj3DocumentText = await csproj3Document.GetTextAsync().ConfigureAwait(false); + var newCsProj3Document = csproj3Document.WithText(csproj3DocumentText.WithChanges(new TextChange(ProtocolConversions.RangeToTextSpan(caretLocation, csproj3DocumentText), "C"))); + await testLspServer.TestWorkspace.ChangeDocumentAsync(csproj3Document.Id, newCsProj3Document.Project.Solution).ConfigureAwait(false); + + // Get updated workspace diagnostics for the change. + var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); + results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResultIds).ConfigureAwait(false); + AssertEx.NotNull(results); + Assert.Equal(6, results.Length); + + // Verify that new diagnostics are returned for all files (even though the diagnostics for the first two files are the same) + // since we re-calculate when transitive project dependencies change. + Assert.Empty(results[0].Diagnostics); + Assert.NotEqual(previousResultIds[0].resultId, results[0].ResultId); + Assert.Empty(results[1].Diagnostics); + Assert.NotEqual(previousResultIds[1].resultId, results[1].ResultId); + + Assert.Empty(results[2].Diagnostics); + Assert.NotEqual(previousResultIds[2].resultId, results[2].ResultId); + Assert.Empty(results[3].Diagnostics); + Assert.NotEqual(previousResultIds[3].resultId, results[3].ResultId); + + Assert.Empty(results[4].Diagnostics); + Assert.NotEqual(previousResultIds[4].resultId, results[4].ResultId); + Assert.Empty(results[5].Diagnostics); + Assert.NotEqual(previousResultIds[5].resultId, results[5].ResultId); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsWithChangeInNotReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsWithChangeInNotReferencedProject(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"namespace M { class A : B { } }"; - var markup2 = + var markup2 = @"namespace M { public class {|caret:|} { } }"; - var workspaceXml = + var workspaceXml = @$" {markup1} @@ -1628,54 +1677,54 @@ public class {|caret:|} { } "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); - var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); - - // Verify we a diagnostic in A.cs since B does not exist - // and a diagnostic in B.cs since it is missing the class name. - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - AssertEx.NotNull(results); - Assert.Equal(4, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); - Assert.Empty(results[1].Diagnostics); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); - Assert.Empty(results[3].Diagnostics); - - // Insert B into B.cs via the workspace. - var caretLocation = testLspServer.GetLocations("caret").First().Range; - var csproj2DocumentText = await csproj2Document.GetTextAsync(); - var newCsProj2Document = csproj2Document.WithText(csproj2DocumentText.WithChanges(new TextChange(ProtocolConversions.RangeToTextSpan(caretLocation, csproj2DocumentText), "B"))); - await testLspServer.TestWorkspace.ChangeDocumentAsync(csproj2Document.Id, newCsProj2Document.Project.Solution); - - // Get updated workspace diagnostics for the change. - var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); - results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResultIds); - AssertEx.NotNull(results); - Assert.Equal(2, results.Length); - - // Note: tehre will be no results for A.cs as it is unchanged and does not reference CSProj2. - // Verify that the diagnostics result for B.cs reflects the change we made to it. - Assert.Empty(results[0].Diagnostics); - Assert.NotEqual(previousResultIds[2].resultId, results[0].ResultId); - Assert.Empty(results[1].Diagnostics); - Assert.NotEqual(previousResultIds[3].resultId, results[1].ResultId); - } + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); + var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); + + // Verify we a diagnostic in A.cs since B does not exist + // and a diagnostic in B.cs since it is missing the class name. + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + AssertEx.NotNull(results); + Assert.Equal(4, results.Length); + Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Empty(results[1].Diagnostics); + Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + Assert.Empty(results[3].Diagnostics); + + // Insert B into B.cs via the workspace. + var caretLocation = testLspServer.GetLocations("caret").First().Range; + var csproj2DocumentText = await csproj2Document.GetTextAsync(); + var newCsProj2Document = csproj2Document.WithText(csproj2DocumentText.WithChanges(new TextChange(ProtocolConversions.RangeToTextSpan(caretLocation, csproj2DocumentText), "B"))); + await testLspServer.TestWorkspace.ChangeDocumentAsync(csproj2Document.Id, newCsProj2Document.Project.Solution); + + // Get updated workspace diagnostics for the change. + var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); + results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResultIds); + AssertEx.NotNull(results); + Assert.Equal(2, results.Length); + + // Note: tehre will be no results for A.cs as it is unchanged and does not reference CSProj2. + // Verify that the diagnostics result for B.cs reflects the change we made to it. + Assert.Empty(results[0].Diagnostics); + Assert.NotEqual(previousResultIds[2].resultId, results[0].ResultId); + Assert.Empty(results[1].Diagnostics); + Assert.NotEqual(previousResultIds[3].resultId, results[1].ResultId); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsWithDependentProjectReloadedAndChanged(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsWithDependentProjectReloadedAndChanged(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"namespace M { class A : B { } }"; - var markup2 = + var markup2 = @"namespace M { public class {|caret:|} { } }"; - var workspaceXml = + var workspaceXml = @$" {markup1} @@ -1686,51 +1735,51 @@ public class {|caret:|} { } "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); - var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); - - // Verify we a diagnostic in A.cs since B does not exist - // and a diagnostic in B.cs since it is missing the class name. - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - AssertEx.NotNull(results); - Assert.Equal(4, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); - - // Change and reload the project via the workspace. - var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); - projectInfo = projectInfo.WithCompilationOptions(projectInfo.CompilationOptions!.WithPlatform(Platform.X64)); - testLspServer.TestWorkspace.OnProjectReloaded(projectInfo); - var operations = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); - await operations.GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); - - // Get updated workspace diagnostics for the change. - var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); - results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResultIds); - - AssertEx.NotNull(results); - Assert.Equal(4, results.Length); - - // The diagnostics should have been recalculated for both projects as a referenced project changed. - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); - } + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); + var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); + + // Verify we a diagnostic in A.cs since B does not exist + // and a diagnostic in B.cs since it is missing the class name. + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + AssertEx.NotNull(results); + Assert.Equal(4, results.Length); + Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + + // Change and reload the project via the workspace. + var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); + projectInfo = projectInfo.WithCompilationOptions(projectInfo.CompilationOptions!.WithPlatform(Platform.X64)); + testLspServer.TestWorkspace.OnProjectReloaded(projectInfo); + var operations = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); + await operations.GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); + + // Get updated workspace diagnostics for the change. + var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); + results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResultIds); + + AssertEx.NotNull(results); + Assert.Equal(4, results.Length); + + // The diagnostics should have been recalculated for both projects as a referenced project changed. + Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsWithDependentProjectReloadedUnchanged(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsWithDependentProjectReloadedUnchanged(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"namespace M { class A : B { } }"; - var markup2 = + var markup2 = @"namespace M { public class {|caret:|} { } }"; - var workspaceXml = + var workspaceXml = @$" {markup1} @@ -1741,44 +1790,44 @@ public class {|caret:|} { } "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); - var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); - - // Verify we a diagnostic in A.cs since B does not exist - // and a diagnostic in B.cs since it is missing the class name. - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - AssertEx.NotNull(results); - Assert.Equal(4, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); - - // Reload the project via the workspace. - var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); - testLspServer.TestWorkspace.OnProjectReloaded(projectInfo); - var operations = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); - await operations.GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); - - // Get updated workspace diagnostics for the change. - var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); - results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResultIds); - - // Verify that since no actual changes have been made we report unchanged diagnostics. - // We get an empty array here as this is workspace diagnostics, and we do not report unchanged - // docs there for efficiency. - AssertEx.NotNull(results); - Assert.Empty(results); - } + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); + var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); + + // Verify we a diagnostic in A.cs since B does not exist + // and a diagnostic in B.cs since it is missing the class name. + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + AssertEx.NotNull(results); + Assert.Equal(4, results.Length); + Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + + // Reload the project via the workspace. + var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); + testLspServer.TestWorkspace.OnProjectReloaded(projectInfo); + var operations = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); + await operations.GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); + + // Get updated workspace diagnostics for the change. + var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); + results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResultIds); + + // Verify that since no actual changes have been made we report unchanged diagnostics. + // We get an empty array here as this is workspace diagnostics, and we do not report unchanged + // docs there for efficiency. + AssertEx.NotNull(results); + Assert.Empty(results); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsOrderOfReferencedProjectsReloadedDoesNotMatter(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsOrderOfReferencedProjectsReloadedDoesNotMatter(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"namespace M { class A : B { } }"; - var workspaceXml = + var workspaceXml = @$" {markup1} @@ -1793,40 +1842,40 @@ class A : B { } "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); - var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); - - // Verify we a diagnostic in A.cs since B does not exist - // and a diagnostic in B.cs since it is missing the class name. - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - AssertEx.NotNull(results); - Assert.Equal(6, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); - - // Reload the project via the workspace. - var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); - testLspServer.TestWorkspace.OnProjectReloaded(projectInfo); - var operations = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); - await operations.GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); - - // Get updated workspace diagnostics for the change. - var previousResults = CreateDiagnosticParamsFromPreviousReports(results); - var previousResultIds = previousResults.Select(param => param.resultId).ToImmutableArray(); - results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResults); - - // Verify that since no actual changes have been made we report unchanged diagnostics. - // We get an empty array here as this is workspace diagnostics, and we do not report unchanged - // docs there for efficiency. - AssertEx.NotNull(results); - Assert.Empty(results); - } + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); + var csproj2Document = testLspServer.GetCurrentSolution().Projects.Where(p => p.Name == "CSProj2").Single().Documents.First(); + + // Verify we a diagnostic in A.cs since B does not exist + // and a diagnostic in B.cs since it is missing the class name. + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + AssertEx.NotNull(results); + Assert.Equal(6, results.Length); + Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + + // Reload the project via the workspace. + var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); + testLspServer.TestWorkspace.OnProjectReloaded(projectInfo); + var operations = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); + await operations.GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); + + // Get updated workspace diagnostics for the change. + var previousResults = CreateDiagnosticParamsFromPreviousReports(results); + var previousResultIds = previousResults.Select(param => param.resultId).ToImmutableArray(); + results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResults); + + // Verify that since no actual changes have been made we report unchanged diagnostics. + // We get an empty array here as this is workspace diagnostics, and we do not report unchanged + // docs there for efficiency. + AssertEx.NotNull(results); + Assert.Empty(results); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsDoesNotThrowIfProjectWithoutFilePathExists(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var csharpMarkup = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsDoesNotThrowIfProjectWithoutFilePathExists(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var csharpMarkup = @"class A {"; - var workspaceXml = + var workspaceXml = @$" {csharpMarkup} @@ -1836,64 +1885,124 @@ public async Task TestWorkspaceDiagnosticsDoesNotThrowIfProjectWithoutFilePathEx "; - await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); + await using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics).ConfigureAwait(false); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(3, results.Length); - Assert.Equal(@"C:/C.cs", results[0].TextDocument.Uri.AbsolutePath); - Assert.Equal(@"C:/CSProj1.csproj", results[1].TextDocument.Uri.AbsolutePath); - Assert.Equal(@"C:/C2.cs", results[2].TextDocument.Uri.AbsolutePath); - } + Assert.Equal(3, results.Length); + Assert.Equal(@"C:/C.cs", results[0].TextDocument.Uri.AbsolutePath); + Assert.Equal(@"C:/CSProj1.csproj", results[1].TextDocument.Uri.AbsolutePath); + Assert.Equal(@"C:/C2.cs", results[2].TextDocument.Uri.AbsolutePath); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsWaitsForLspTextChanges(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsWaitsForLspTextChanges(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - var resultTask = RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, useProgress: true, triggerConnectionClose: false); + var resultTask = RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, useProgress: true, triggerConnectionClose: false); - // Assert that the connection isn't closed and task doesn't complete even after some delay. - await Task.Delay(TimeSpan.FromSeconds(5)); - Assert.False(resultTask.IsCompleted); + // Assert that the connection isn't closed and task doesn't complete even after some delay. + await Task.Delay(TimeSpan.FromSeconds(5)); + Assert.False(resultTask.IsCompleted); - // Make an LSP document change that will trigger connection close. - var uri = testLspServer.GetCurrentSolution().Projects.First().Documents.First().GetURI(); - await testLspServer.OpenDocumentAsync(uri); + // Make an LSP document change that will trigger connection close. + var uri = testLspServer.GetCurrentSolution().Projects.First().Documents.First().GetURI(); + await testLspServer.OpenDocumentAsync(uri); - // Assert the task completes after a change occurs - var results = await resultTask; - Assert.NotEmpty(results); - } + // Assert the task completes after a change occurs + var results = await resultTask; + Assert.NotEmpty(results); + } - [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsWaitsForLspSolutionChanges(bool useVSDiagnostics, bool mutatingLspWorkspace) - { - var markup1 = + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsWaitsForLspSolutionChanges(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = +@"class A {"; + var markup2 = ""; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1, markup2], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + + var resultTask = RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, useProgress: true, triggerConnectionClose: false); + + // Assert that the connection isn't closed and task doesn't complete even after some delay. + await Task.Delay(TimeSpan.FromSeconds(5)); + Assert.False(resultTask.IsCompleted); + + // Make workspace change that will trigger connection close. + var projectInfo = testLspServer.TestWorkspace.Projects.Single().ToProjectInfo(); + testLspServer.TestWorkspace.OnProjectReloaded(projectInfo); + + // Assert the task completes after a change occurs + var results = await resultTask; + Assert.NotEmpty(results); + } + + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsForClosedFilesSwitchFSAFromOnToOff(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = @"class A {"; - var markup2 = ""; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - new[] { markup1, markup2 }, mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + + Assert.Equal(2, results.Length); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Empty(results[1].Diagnostics); - var resultTask = RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, useProgress: true, triggerConnectionClose: false); + var options = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); + options.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.OpenFiles); + options.SetGlobalOption(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.CSharp, CompilerDiagnosticsScope.OpenFiles); - // Assert that the connection isn't closed and task doesn't complete even after some delay. - await Task.Delay(TimeSpan.FromSeconds(5)); - Assert.False(resultTask.IsCompleted); + results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); - // Make workspace change that will trigger connection close. - var projectInfo = testLspServer.TestWorkspace.Projects.Single().ToProjectInfo(); - testLspServer.TestWorkspace.OnProjectReloaded(projectInfo); + Assert.Equal(2, results.Length); - // Assert the task completes after a change occurs - var results = await resultTask; - Assert.NotEmpty(results); + Assert.Null(results[0].ResultId); + Assert.Null(results[1].ResultId); + + // VS represents removal with null diagnostics, VS code represents with an empty diagnostics array. + if (useVSDiagnostics) + { + Assert.Null(results[0].Diagnostics); + Assert.Null(results[1].Diagnostics); } + else + { + Assert.Empty(results[0].Diagnostics); + Assert.Empty(results[1].Diagnostics); + } + } + + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsForClosedFilesSwitchFSAFromOffToOn(bool useVSDiagnostics, bool mutatingLspWorkspace) + { + var markup1 = +@"class A {"; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - #endregion + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + + Assert.Equal(0, results.Length); + + var options = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); + options.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); + options.SetGlobalOption(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.CSharp, CompilerDiagnosticsScope.FullSolution); + + results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); + + Assert.Equal(2, results.Length); + Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Empty(results[1].Diagnostics); } + + #endregion } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs index 09ab03f8ac5fb..7e864c3c83a85 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs @@ -107,7 +107,8 @@ void M() { await DidOpen(testLspServer, locationTyped.Uri); - await Assert.ThrowsAsync(() => DidOpen(testLspServer, locationTyped.Uri)); + await Assert.ThrowsAnyAsync(() => DidOpen(testLspServer, locationTyped.Uri)); + await testLspServer.AssertServerShuttingDownAsync(); } } @@ -126,7 +127,8 @@ void M() await using (testLspServer) { - await Assert.ThrowsAsync(() => DidClose(testLspServer, locationTyped.Uri)); + await Assert.ThrowsAnyAsync(() => DidClose(testLspServer, locationTyped.Uri)); + await testLspServer.AssertServerShuttingDownAsync(); } } @@ -145,7 +147,8 @@ void M() await using (testLspServer) { - await Assert.ThrowsAsync(() => DidChange(testLspServer, locationTyped.Uri, (0, 0, "goo"))); + await Assert.ThrowsAnyAsync(() => DidChange(testLspServer, locationTyped.Uri, (0, 0, "goo"))); + await testLspServer.AssertServerShuttingDownAsync(); } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs new file mode 100644 index 0000000000000..adb5bc1e36834 --- /dev/null +++ b/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs @@ -0,0 +1,302 @@ +// Licensed to the .NET Foundation under one or more 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.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CommonLanguageServerProtocol.Framework; +using Newtonsoft.Json; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Test.Utilities; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests +{ + [UseExportProvider] + public class HandlerTests : AbstractLanguageServerProtocolTests + { + public HandlerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + } + + protected override TestComposition Composition => base.Composition.AddParts( + typeof(TestDocumentHandler), + typeof(TestRequestHandlerWithNoParams), + typeof(TestNotificationHandlerFactory), + typeof(TestNotificationWithoutParamsHandlerFactory), + typeof(TestLanguageSpecificHandler), + typeof(TestLanguageSpecificHandlerWithDifferentParams)); + + [Theory, CombinatorialData] + public async Task CanExecuteRequestHandler(bool mutatingLspWorkspace) + { + await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); + + var request = new TestRequestTypeOne(new TextDocumentIdentifier + { + Uri = ProtocolConversions.CreateAbsoluteUri(@"C:\test.cs") + }); + var response = await server.ExecuteRequestAsync(TestDocumentHandler.MethodName, request, CancellationToken.None); + Assert.Equal(typeof(TestDocumentHandler).Name, response); + } + + [Theory, CombinatorialData] + public async Task CanExecuteRequestHandlerWithNoParams(bool mutatingLspWorkspace) + { + await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); + + var response = await server.ExecuteRequest0Async(TestRequestHandlerWithNoParams.MethodName, CancellationToken.None); + Assert.Equal(typeof(TestRequestHandlerWithNoParams).Name, response); + } + + [Theory, CombinatorialData] + public async Task CanExecuteNotificationHandler(bool mutatingLspWorkspace) + { + await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); + + var request = new TestRequestTypeOne(new TextDocumentIdentifier + { + Uri = ProtocolConversions.CreateAbsoluteUri(@"C:\test.cs") + }); + + await server.ExecuteNotificationAsync(TestNotificationHandler.MethodName, request); + var response = await server.GetRequiredLspService().ResultSource.Task; + Assert.Equal(typeof(TestNotificationHandler).Name, response); + } + + [Theory, CombinatorialData] + public async Task CanExecuteNotificationHandlerWithNoParams(bool mutatingLspWorkspace) + { + await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); + + await server.ExecuteNotification0Async(TestNotificationWithoutParamsHandler.MethodName); + var response = await server.GetRequiredLspService().ResultSource.Task; + Assert.Equal(typeof(TestNotificationWithoutParamsHandler).Name, response); + } + + [Theory, CombinatorialData] + public async Task CanExecuteLanguageSpecificHandler(bool mutatingLspWorkspace) + { + await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); + + var request = new TestRequestTypeOne(new TextDocumentIdentifier + { + Uri = ProtocolConversions.CreateAbsoluteUri(@"C:\test.fs") + }); + var response = await server.ExecuteRequestAsync(TestDocumentHandler.MethodName, request, CancellationToken.None); + Assert.Equal(typeof(TestLanguageSpecificHandler).Name, response); + } + + [Theory, CombinatorialData] + public async Task CanExecuteLanguageSpecificHandlerWithDifferentRequestTypes(bool mutatingLspWorkspace) + { + await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); + + var request = new TestRequestTypeTwo(new TextDocumentIdentifier + { + Uri = ProtocolConversions.CreateAbsoluteUri(@"C:\test.vb") + }); + var response = await server.ExecuteRequestAsync(TestDocumentHandler.MethodName, request, CancellationToken.None); + Assert.Equal(typeof(TestLanguageSpecificHandlerWithDifferentParams).Name, response); + } + + [Theory, CombinatorialData] + public async Task ThrowsOnInvalidLanguageSpecificHandler(bool mutatingLspWorkspace) + { + // Arrange + await Assert.ThrowsAsync(async () => await CreateTestLspServerAsync("", mutatingLspWorkspace, + composition: Composition.AddParts(typeof(TestDuplicateLanguageSpecificHandler)))); + } + + [Theory, CombinatorialData] + public async Task ThrowsIfDeserializationFails(bool mutatingLspWorkspace) + { + await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); + + var request = new TestRequestTypeThree("value"); + await Assert.ThrowsAsync(async () => await server.ExecuteRequestAsync(TestDocumentHandler.MethodName, request, CancellationToken.None)); + } + + [DataContract] + internal record TestRequestTypeOne([property: DataMember(Name = "textDocument"), JsonProperty(Required = Required.Always)] TextDocumentIdentifier TextDocumentIdentifier); + + [DataContract] + internal record TestRequestTypeTwo([property: DataMember(Name = "textDocument"), JsonProperty(Required = Required.Always)] TextDocumentIdentifier TextDocumentIdentifier); + + [DataContract] + internal record TestRequestTypeThree([property: DataMember(Name = "someValue")] string SomeValue); + + [ExportCSharpVisualBasicStatelessLspService(typeof(TestDocumentHandler)), PartNotDiscoverable, Shared] + [LanguageServerEndpoint(MethodName, LanguageServerConstants.DefaultLanguageName)] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestDocumentHandler() : ILspServiceDocumentRequestHandler + { + public const string MethodName = nameof(TestDocumentHandler); + + public bool MutatesSolutionState => true; + public bool RequiresLSPSolution => true; + + public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequestTypeOne request) + { + return request.TextDocumentIdentifier; + } + + public Task HandleRequestAsync(TestRequestTypeOne request, RequestContext context, CancellationToken cancellationToken) + { + return Task.FromResult(this.GetType().Name); + } + } + + [ExportCSharpVisualBasicStatelessLspService(typeof(TestRequestHandlerWithNoParams)), PartNotDiscoverable, Shared] + [LanguageServerEndpoint(MethodName, LanguageServerConstants.DefaultLanguageName)] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestRequestHandlerWithNoParams() : ILspServiceRequestHandler + { + public const string MethodName = nameof(TestRequestHandlerWithNoParams); + + public bool MutatesSolutionState => true; + public bool RequiresLSPSolution => true; + + public Task HandleRequestAsync(RequestContext context, CancellationToken cancellationToken) + { + return Task.FromResult(this.GetType().Name); + } + } + + [LanguageServerEndpoint(MethodName, LanguageServerConstants.DefaultLanguageName)] + internal sealed class TestNotificationHandler() : ILspServiceNotificationHandler + { + public const string MethodName = nameof(TestNotificationHandler); + public readonly TaskCompletionSource ResultSource = new(); + + public bool MutatesSolutionState => true; + public bool RequiresLSPSolution => true; + + public Task HandleNotificationAsync(TestRequestTypeOne request, RequestContext context, CancellationToken cancellationToken) + { + ResultSource.SetResult(this.GetType().Name); + return Task.CompletedTask; + } + } + + /// + /// Exported via a factory as we need a new instance for each server (the task completion result should be unique per server). + /// + [ExportCSharpVisualBasicLspServiceFactory(typeof(TestNotificationHandler)), PartNotDiscoverable, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestNotificationHandlerFactory() : ILspServiceFactory + { + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + { + return new TestNotificationHandler(); + } + } + + [LanguageServerEndpoint(MethodName, LanguageServerConstants.DefaultLanguageName)] + internal sealed class TestNotificationWithoutParamsHandler() : ILspServiceNotificationHandler + { + public const string MethodName = nameof(TestNotificationWithoutParamsHandler); + public readonly TaskCompletionSource ResultSource = new(); + + public bool MutatesSolutionState => true; + public bool RequiresLSPSolution => true; + + public Task HandleNotificationAsync(RequestContext context, CancellationToken cancellationToken) + { + ResultSource.SetResult(this.GetType().Name); + return Task.CompletedTask; + } + } + + /// + /// Exported via a factory as we need a new instance for each server (the task completion result should be unique per server). + /// + [ExportCSharpVisualBasicLspServiceFactory(typeof(TestNotificationWithoutParamsHandler)), PartNotDiscoverable, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestNotificationWithoutParamsHandlerFactory() : ILspServiceFactory + { + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + { + return new TestNotificationWithoutParamsHandler(); + } + } + + /// + /// Defines a language specific handler with the same method as + /// + [ExportCSharpVisualBasicStatelessLspService(typeof(TestLanguageSpecificHandler)), PartNotDiscoverable, Shared] + [LanguageServerEndpoint(TestDocumentHandler.MethodName, LanguageNames.FSharp)] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestLanguageSpecificHandler() : ILspServiceDocumentRequestHandler + { + public bool MutatesSolutionState => true; + public bool RequiresLSPSolution => true; + + public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequestTypeOne request) + { + return request.TextDocumentIdentifier; + } + + public Task HandleRequestAsync(TestRequestTypeOne request, RequestContext context, CancellationToken cancellationToken) + { + return Task.FromResult(this.GetType().Name); + } + } + + /// + /// Defines a language specific handler with the same method as + /// but using different request and response types. + /// + [ExportCSharpVisualBasicStatelessLspService(typeof(TestLanguageSpecificHandlerWithDifferentParams)), PartNotDiscoverable, Shared] + [LanguageServerEndpoint(TestDocumentHandler.MethodName, LanguageNames.VisualBasic)] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestLanguageSpecificHandlerWithDifferentParams() : ILspServiceDocumentRequestHandler + { + public bool MutatesSolutionState => true; + public bool RequiresLSPSolution => true; + + public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequestTypeTwo request) + { + return request.TextDocumentIdentifier; + } + + public Task HandleRequestAsync(TestRequestTypeTwo request, RequestContext context, CancellationToken cancellationToken) + { + return Task.FromResult(this.GetType().Name); + } + } + + /// + /// Defines a language specific handler with the same method and language as + /// but with different params (an error) + /// + [ExportCSharpVisualBasicStatelessLspService(typeof(TestDuplicateLanguageSpecificHandler)), PartNotDiscoverable, Shared] + [LanguageServerEndpoint(TestDocumentHandler.MethodName, LanguageNames.FSharp)] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestDuplicateLanguageSpecificHandler() : ILspServiceRequestHandler + { + public bool MutatesSolutionState => true; + public bool RequiresLSPSolution => true; + + public Task HandleRequestAsync(RequestContext context, CancellationToken cancellationToken) + { + return Task.FromResult(this.GetType().Name); + } + } + } +} diff --git a/src/Features/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs index 2e0b0d75ea3c9..b9faef82ff489 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/LanguageServerTargetTests.cs @@ -34,7 +34,7 @@ public async Task LanguageServerQueueEmptyOnShutdownMessage(bool mutatingLspWork AssertServerAlive(server); await server.ShutdownTestServerAsync(); - await AssertServerQueueClosed(server).ConfigureAwait(false); + await server.AssertServerShuttingDownAsync().ConfigureAwait(false); Assert.False(server.GetServerAccessor().GetServerRpc().IsDisposed); await server.ExitTestServerAsync(); } @@ -47,7 +47,7 @@ public async Task LanguageServerCleansUpOnExitMessage(bool mutatingLspWorkspace) await server.ShutdownTestServerAsync(); await server.ExitTestServerAsync(); - await AssertServerQueueClosed(server).ConfigureAwait(false); + await server.AssertServerShuttingDownAsync().ConfigureAwait(false); Assert.True(server.GetServerAccessor().GetServerRpc().IsDisposed); } @@ -58,7 +58,7 @@ public async Task LanguageServerCleansUpOnUnexpectedJsonRpcDisconnectAsync(bool AssertServerAlive(server); server.GetServerAccessor().GetServerRpc().Dispose(); - await AssertServerQueueClosed(server).ConfigureAwait(false); + await server.AssertServerShuttingDownAsync().ConfigureAwait(false); Assert.True(server.GetServerAccessor().GetServerRpc().IsDisposed); } @@ -143,14 +143,6 @@ private static void AssertServerAlive(TestLspServer server) Assert.False(server.GetQueueAccessor()!.Value.IsComplete()); } - private static async Task AssertServerQueueClosed(TestLspServer server) - { - var queueAccessor = server.GetQueueAccessor()!.Value; - await queueAccessor.WaitForProcessingToStopAsync().ConfigureAwait(false); - Assert.True(server.GetServerAccessor().HasShutdownStarted()); - Assert.True(queueAccessor.IsComplete()); - } - [ExportCSharpVisualBasicLspServiceFactory(typeof(StatefulLspService)), Shared] internal class StatefulLspServiceFactory : ILspServiceFactory { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/ProtocolConversionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/ProtocolConversionsTests.cs index 8685d125fc2a5..a9d20fa0ccee6 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/ProtocolConversionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/ProtocolConversionsTests.cs @@ -103,6 +103,47 @@ public void CreateAbsoluteUri_LocalPaths_Unix(string filePath, string expectedAb Assert.Equal(filePath, uri.LocalPath); } + [ConditionalTheory(typeof(WindowsOnly))] + [InlineData("C:\\a\\b", "file:///C:/a/b")] + [InlineData("C:\\a\\b\\", "file:///C:/a/b")] + [InlineData("C:\\a\\\\b", "file:///C:/a//b")] + [InlineData("C:\\%25\ue25b/a\\b", "file:///C:/%2525%EE%89%9B/a/b")] + [InlineData("C:\\%25\ue25b/a\\\\b", "file:///C:/%2525%EE%89%9B/a//b")] + [InlineData("C:\\\u0089\uC7BD", "file:///C:/%C2%89%EC%9E%BD")] + [InlineData("/\\server\ue25b\\%25\ue25b\\b", "file://server/%2525%EE%89%9B/b")] + [InlineData("\\\\server\ue25b\\%25\ue25b\\b", "file://server/%2525%EE%89%9B/b")] + [InlineData("\\\\server\ue25b\\%25\ue25b\\b\\", "file://server/%2525%EE%89%9B/b")] + [InlineData("C:\\ !$&'()+,-;=@[]_~#", "file:///C:/%20!$&'()+,-;=@[]_~%23")] + [InlineData("C:\\ !$&'()+,-;=@[]_~#\ue25b", "file:///C:/%20!$&'()+,-;=@[]_~%23%EE%89%9B")] + [InlineData("C:\\\u0073\u0323\u0307", "file:///C:/s%CC%A3%CC%87")] // combining marks + [InlineData("A:/\\\u200e//", "file:///A://%E2%80%8E//")] // cases from https://github.com/dotnet/runtime/issues/1487 + [InlineData("B:\\/\u200e", "file:///B://%E2%80%8E")] + [InlineData("C:/\\\\-Ā\r", "file:///C:///-%C4%80%0D")] + [InlineData("D:\\\\\\\\\\\u200e", "file:///D://///%E2%80%8E")] + public void CreateRelativePatternBaseUri_LocalPaths_Windows(string filePath, string expectedUri) + { + var uri = ProtocolConversions.CreateRelativePatternBaseUri(filePath); + Assert.Equal(expectedUri, uri.AbsoluteUri); + } + + [ConditionalTheory(typeof(UnixLikeOnly))] + [InlineData("/", "file://")] + [InlineData("/u", "file:///u")] + [InlineData("/unix/", "file:///unix")] + [InlineData("/unix/path", "file:///unix/path")] + [InlineData("/%25\ue25b/\u0089\uC7BD", "file:///%2525%EE%89%9B/%C2%89%EC%9E%BD")] + [InlineData("/!$&'()+,-;=@[]_~#", "file:///!$&'()+,-;=@[]_~%23")] + [InlineData("/!$&'()+,-;=@[]_~#", "file:///!$&'()+,-;=@[]_~%23%EE%89%9B")] + [InlineData("/\\\u200e//", "file:////%E2%80%8E//")] // cases from https://github.com/dotnet/runtime/issues/1487 + [InlineData("\\/\u200e", "file:////%E2%80%8E")] + [InlineData("/\\\\-Ā\r", "file://///-%C4%80%0D")] + [InlineData("\\\\\\\\\\\u200e", "file:///////%E2%80%8E")] + public void CreateRelativePatternBaseUri_LocalPaths_Unix(string filePath, string expectedRelativeUri) + { + var uri = ProtocolConversions.CreateRelativePatternBaseUri(filePath); + Assert.Equal(expectedRelativeUri, uri.AbsoluteUri); + } + [ConditionalTheory(typeof(UnixLikeOnly))] [InlineData("/a/./b", "file:///a/./b", "file:///a/b")] [InlineData("/a/../b", "file:///a/../b", "file:///b")] diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs index ed5bbf322c5e3..f4abf45dec3ce 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs @@ -181,7 +181,7 @@ private static LSP.DocumentSymbol CreateDocumentSymbol(LSP.SymbolKind kind, stri { var children = parent.Children.ToList(); children.Add(documentSymbol); - parent.Children = children.ToArray(); + parent.Children = [.. children]; } return documentSymbol; diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs index 3b1225e6ba1db..11db567b17042 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs @@ -24,13 +24,13 @@ private static void AssertSetEquals(LSP.SymbolInformation[] expected, LSP.Symbol => Assert.True(expected.ToHashSet().SetEquals(results)); private Task CreateTestLspServerAsync(string markup, bool mutatingLspWorkspace) - => CreateTestLspServerAsync(markup, mutatingLspWorkspace, extraExportedTypes: [typeof(TestWorkspaceNavigateToSearchHostService)]); + => CreateTestLspServerAsync(markup, mutatingLspWorkspace, composition: Composition.AddParts(typeof(TestWorkspaceNavigateToSearchHostService))); private Task CreateVisualBasicTestLspServerAsync(string markup, bool mutatingLspWorkspace) - => CreateVisualBasicTestLspServerAsync(markup, mutatingLspWorkspace, extraExportedTypes: [typeof(TestWorkspaceNavigateToSearchHostService)]); + => CreateVisualBasicTestLspServerAsync(markup, mutatingLspWorkspace, composition: Composition.AddParts(typeof(TestWorkspaceNavigateToSearchHostService))); private Task CreateTestLspServerAsync(string[] markups, bool mutatingLspWorkspace) - => CreateTestLspServerAsync(markups, mutatingLspWorkspace, extraExportedTypes: [typeof(TestWorkspaceNavigateToSearchHostService)]); + => CreateTestLspServerAsync(markups, mutatingLspWorkspace, composition: Composition.AddParts(typeof(TestWorkspaceNavigateToSearchHostService))); [Theory, CombinatorialData] public async Task TestGetWorkspaceSymbolsAsync_Class(bool mutatingLspWorkspace) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/UriTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/UriTests.cs index 863040cb99abe..853bae0808727 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/UriTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/UriTests.cs @@ -79,15 +79,17 @@ public async Task TestWorkspaceDocument_WithFileScheme(bool mutatingLspWorkspace { var documentFilePath = @"C:\A.cs"; var markup = -@$" - - - public class A - {{ - }} - - -"; + $$""" + + + + public class A + { + } + + + + """; await using var testLspServer = await CreateXmlTestLspServerAsync(markup, mutatingLspWorkspace); var workspaceDocument = testLspServer.TestWorkspace.CurrentSolution.Projects.Single().Documents.Single(); @@ -95,12 +97,25 @@ public class A await testLspServer.OpenDocumentAsync(expectedDocumentUri).ConfigureAwait(false); - // Verify file is added to the misc file workspace. - var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { Uri = expectedDocumentUri }, CancellationToken.None); - Assert.False(workspace is LspMiscellaneousFilesWorkspace); - AssertEx.NotNull(document); - Assert.Equal(expectedDocumentUri, document.GetURI()); - Assert.Equal(documentFilePath, document.FilePath); + // Verify file is not added to the misc file workspace. + { + var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { Uri = expectedDocumentUri }, CancellationToken.None); + Assert.False(workspace is LspMiscellaneousFilesWorkspace); + AssertEx.NotNull(document); + Assert.Equal(expectedDocumentUri, document.GetURI()); + Assert.Equal(documentFilePath, document.FilePath); + } + + // Try again, this time with a uri with different case sensitivity. This is supported, and is needed by Xaml. + { + var lowercaseUri = ProtocolConversions.CreateAbsoluteUri(documentFilePath.ToLowerInvariant()); + Assert.NotEqual(expectedDocumentUri.AbsolutePath, lowercaseUri.AbsolutePath); + var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { Uri = lowercaseUri }, CancellationToken.None); + Assert.False(workspace is LspMiscellaneousFilesWorkspace); + AssertEx.NotNull(document); + Assert.Equal(expectedDocumentUri, document.GetURI()); + Assert.Equal(documentFilePath, document.FilePath); + } } [Theory, CombinatorialData] diff --git a/src/Features/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs index e67b602ccc0b1..73f22e2e68fc3 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs @@ -111,7 +111,8 @@ private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Str var capabilitiesProvider = workspace.ExportProvider.GetExportedValue(); var servicesProvider = workspace.ExportProvider.GetExportedValue(); - var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream)) + var messageFormatter = CreateJsonMessageFormatter(); + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, messageFormatter)) { ExceptionStrategy = ExceptionProcessing.ISerializable, }; @@ -119,7 +120,7 @@ private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Str var logger = NoOpLspLogger.Instance; var languageServer = new RoslynLanguageServer( - servicesProvider, jsonRpc, + servicesProvider, jsonRpc, messageFormatter.JsonSerializer, capabilitiesProvider, logger, workspace.Services.HostServices, diff --git a/src/Features/Lsif/Generator/Generator.cs b/src/Features/Lsif/Generator/Generator.cs index 61929ec437f86..87db8cfbcc8f6 100644 --- a/src/Features/Lsif/Generator/Generator.cs +++ b/src/Features/Lsif/Generator/Generator.cs @@ -86,7 +86,7 @@ public static Generator CreateAndWriteCapabilitiesVertex(ILsifJsonWriter lsifJso DocumentSymbolProvider, FoldingRangeProvider, DiagnosticProvider, - new SemanticTokensCapabilities(SemanticTokensSchema.LegacyTokensSchemaForLSIF.AllTokenTypes, [SemanticTokenModifiers.Static])); + new SemanticTokensCapabilities(SemanticTokensSchema.LegacyTokensSchemaForLSIF.AllTokenTypes, [SemanticTokenModifiers.Static, SemanticTokenModifiers.Deprecated])); generator._lsifJsonWriter.Write(capabilitiesVertex); return generator; } diff --git a/src/Features/Lsif/Generator/Graph/Edge.cs b/src/Features/Lsif/Generator/Graph/Edge.cs index 57d05b9ad1caa..1a10b36ecf2dd 100644 --- a/src/Features/Lsif/Generator/Graph/Edge.cs +++ b/src/Features/Lsif/Generator/Graph/Edge.cs @@ -25,7 +25,7 @@ internal class Edge : Element [JsonProperty("inVs", NullValueHandling = NullValueHandling.Ignore)] public Id[]? InVertices { get; } - public IEnumerable> GetInVerticies() => InVertices ?? SpecializedCollections.SingletonEnumerable(InVertex!.Value); + public IEnumerable> GetInVerticies() => InVertices ?? [InVertex!.Value]; public Edge(string label, Id outVertex, Id inVertex, IdFactory idFactory) : base(type: "edge", label: label, idFactory) diff --git a/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb b/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb index 2db321450b74f..5b01bbd5725c8 100644 --- a/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb +++ b/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb @@ -27,7 +27,7 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests , openDocuments:=False, composition:=TestLsifOutput.TestComposition), jsonWriter) AssertEx.EqualOrDiff( -"{""hoverProvider"":true,""declarationProvider"":false,""definitionProvider"":true,""referencesProvider"":true,""typeDefinitionProvider"":false,""documentSymbolProvider"":true,""foldingRangeProvider"":true,""diagnosticProvider"":false,""semanticTokensProvider"":{""tokenTypes"":[""namespace"",""type"",""class"",""enum"",""interface"",""struct"",""typeParameter"",""parameter"",""variable"",""property"",""enumMember"",""event"",""function"",""method"",""macro"",""keyword"",""modifier"",""comment"",""string"",""number"",""regexp"",""operator"",""class name"",""constant name"",""delegate name"",""enum member name"",""enum name"",""event name"",""excluded code"",""extension method name"",""field name"",""interface name"",""json - array"",""json - comment"",""json - constructor name"",""json - keyword"",""json - number"",""json - object"",""json - operator"",""json - property name"",""json - punctuation"",""json - string"",""json - text"",""keyword - control"",""label name"",""local name"",""method name"",""module name"",""namespace name"",""operator - overloaded"",""parameter name"",""preprocessor keyword"",""preprocessor text"",""property name"",""punctuation"",""record class name"",""record struct name"",""regex - alternation"",""regex - anchor"",""regex - character class"",""regex - comment"",""regex - grouping"",""regex - other escape"",""regex - quantifier"",""regex - self escaped character"",""regex - text"",""roslyn test code markdown"",""string - escape character"",""string - verbatim"",""struct name"",""text"",""type parameter name"",""whitespace"",""xml doc comment - attribute name"",""xml doc comment - attribute quotes"",""xml doc comment - attribute value"",""xml doc comment - cdata section"",""xml doc comment - comment"",""xml doc comment - delimiter"",""xml doc comment - entity reference"",""xml doc comment - name"",""xml doc comment - processing instruction"",""xml doc comment - text"",""xml literal - attribute name"",""xml literal - attribute quotes"",""xml literal - attribute value"",""xml literal - cdata section"",""xml literal - comment"",""xml literal - delimiter"",""xml literal - embedded expression"",""xml literal - entity reference"",""xml literal - name"",""xml literal - processing instruction"",""xml literal - text""],""tokenModifiers"":[""static""]},""id"":1,""type"":""vertex"",""label"":""capabilities""} +"{""hoverProvider"":true,""declarationProvider"":false,""definitionProvider"":true,""referencesProvider"":true,""typeDefinitionProvider"":false,""documentSymbolProvider"":true,""foldingRangeProvider"":true,""diagnosticProvider"":false,""semanticTokensProvider"":{""tokenTypes"":[""namespace"",""type"",""class"",""enum"",""interface"",""struct"",""typeParameter"",""parameter"",""variable"",""property"",""enumMember"",""event"",""function"",""method"",""macro"",""keyword"",""modifier"",""comment"",""string"",""number"",""regexp"",""operator"",""class name"",""constant name"",""delegate name"",""enum member name"",""enum name"",""event name"",""excluded code"",""extension method name"",""field name"",""interface name"",""json - array"",""json - comment"",""json - constructor name"",""json - keyword"",""json - number"",""json - object"",""json - operator"",""json - property name"",""json - punctuation"",""json - string"",""json - text"",""keyword - control"",""label name"",""local name"",""method name"",""module name"",""namespace name"",""operator - overloaded"",""parameter name"",""preprocessor keyword"",""preprocessor text"",""property name"",""punctuation"",""record class name"",""record struct name"",""regex - alternation"",""regex - anchor"",""regex - character class"",""regex - comment"",""regex - grouping"",""regex - other escape"",""regex - quantifier"",""regex - self escaped character"",""regex - text"",""roslyn test code markdown"",""string - escape character"",""string - verbatim"",""struct name"",""text"",""type parameter name"",""whitespace"",""xml doc comment - attribute name"",""xml doc comment - attribute quotes"",""xml doc comment - attribute value"",""xml doc comment - cdata section"",""xml doc comment - comment"",""xml doc comment - delimiter"",""xml doc comment - entity reference"",""xml doc comment - name"",""xml doc comment - processing instruction"",""xml doc comment - text"",""xml literal - attribute name"",""xml literal - attribute quotes"",""xml literal - attribute value"",""xml literal - cdata section"",""xml literal - comment"",""xml literal - delimiter"",""xml literal - embedded expression"",""xml literal - entity reference"",""xml literal - name"",""xml literal - processing instruction"",""xml literal - text""],""tokenModifiers"":[""static"",""deprecated""]},""id"":1,""type"":""vertex"",""label"":""capabilities""} {""kind"":""csharp"",""resource"":""file:///Z:/%EE%89%9B/TestProject.csproj"",""name"":""TestProject"",""id"":2,""type"":""vertex"",""label"":""project""} {""kind"":""begin"",""scope"":""project"",""data"":2,""id"":3,""type"":""vertex"",""label"":""$event""} {""uri"":""file:///Z:/%EE%89%9B/a.cs"",""languageId"":""csharp"",""id"":4,""type"":""vertex"",""label"":""document""} @@ -168,7 +168,8 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests ""xml literal - text"" ], ""tokenModifiers"": [ - ""static"" + ""static"", + ""deprecated"" ] }, ""id"": 1, diff --git a/src/Features/RulesMissingDocumentation.md b/src/Features/RulesMissingDocumentation.md index e515219729aa8..0e0f9aa6c62a3 100644 --- a/src/Features/RulesMissingDocumentation.md +++ b/src/Features/RulesMissingDocumentation.md @@ -11,6 +11,7 @@ IDE0302 | | Simplify collection initialization | IDE0304 | | Simplify collection initialization | IDE0305 | | Simplify collection initialization | +IDE0320 | | Make anonymous function static | IDE1007 | | | IDE2000 | | Avoid multiple blank lines | IDE2001 | | Embedded statements must be on their own line | diff --git a/src/Features/Test/EditAndContinue/ActiveStatementsMapTests.cs b/src/Features/Test/EditAndContinue/ActiveStatementsMapTests.cs index afc1d49fcf948..d3bea48ce86cc 100644 --- a/src/Features/Test/EditAndContinue/ActiveStatementsMapTests.cs +++ b/src/Features/Test/EditAndContinue/ActiveStatementsMapTests.cs @@ -151,7 +151,7 @@ ManagedActiveStatementDebugInfo CreateInfo(int startLine, int startColumn, int e "[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}")); + }, oldSpans.Select(s => $"{s.UnmappedSpan} -> {s.Statement.Span} #{s.Statement.Id.Ordinal}")); } [Fact] diff --git a/src/Features/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs b/src/Features/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs index 0c7c6a2013abc..f81968349a25e 100644 --- a/src/Features/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs +++ b/src/Features/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs @@ -66,11 +66,6 @@ public async Task TryGetCompileTimeDocumentAsync(string kind) var compileTimeDocument = await CompileTimeSolutionProvider.TryGetCompileTimeDocumentAsync(designTimeDocument, compileTimeSolution, CancellationToken.None, sourceGeneratedPathPrefix); Assert.Same(sourceGeneratedDoc, compileTimeDocument); - - var actualDesignTimeDocumentIds = await CompileTimeSolutionProvider.GetDesignTimeDocumentsAsync( - compileTimeSolution, ImmutableArray.Create(documentId, sourceGeneratedDoc.Id), designTimeSolution, CancellationToken.None, sourceGeneratedPathPrefix); - - AssertEx.Equal(new[] { documentId, designTimeDocumentId }, actualDesignTimeDocumentIds); } [Fact] diff --git a/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs b/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs index a6c802e7ab9ee..3534e172218e2 100644 --- a/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs +++ b/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs @@ -110,7 +110,7 @@ public static void Main() Assert.False(reader.TryGetDocumentChecksum("/A/C.cs", out _, out _)); Assert.True(reader.TryGetDocumentChecksum("/a/c.cs", out var actualChecksum, out var actualAlgorithm)); - Assert.Equal("21-C8-B2-D7-A3-6B-49-C7-57-DF-67-B8-1F-75-DF-6A-64-FD-59-22", BitConverter.ToString(actualChecksum.ToArray())); + Assert.Equal("21-C8-B2-D7-A3-6B-49-C7-57-DF-67-B8-1F-75-DF-6A-64-FD-59-22", BitConverter.ToString([.. actualChecksum])); Assert.Equal(new Guid("ff1816ec-aa5e-4d10-87f7-6f4963833460"), actualAlgorithm); } } diff --git a/src/Features/Test/EditAndContinue/EditSessionActiveStatementsTests.cs b/src/Features/Test/EditAndContinue/EditSessionActiveStatementsTests.cs index 3c721f2425433..9c36391c5fe08 100644 --- a/src/Features/Test/EditAndContinue/EditSessionActiveStatementsTests.cs +++ b/src/Features/Test/EditAndContinue/EditSessionActiveStatementsTests.cs @@ -7,22 +7,19 @@ 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.CSharp.EditAndContinue; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; -using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.UnitTests; -using Moq; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; -using System.Text; -using System.IO; namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { @@ -52,7 +49,7 @@ private static EditSession CreateEditSession( mockDebuggerService, mockCompilationOutputsProvider, NullPdbMatchingSourceTextProvider.Instance, - SpecializedCollections.EmptyEnumerable>(), + initialDocumentStates: [], reportDiagnostics: true); if (initialState != CommittedSolution.DocumentState.None) @@ -60,7 +57,7 @@ private static EditSession CreateEditSession( EditAndContinueTestHelpers.SetDocumentsState(debuggingSession, solution, initialState); } - debuggingSession.RestartEditSession(nonRemappableRegions ?? ImmutableDictionary>.Empty, inBreakState: true, out _); + debuggingSession.RestartEditSession(nonRemappableRegions ?? ImmutableDictionary>.Empty, inBreakState: true); return debuggingSession.EditSession; } @@ -186,7 +183,7 @@ static void Main() // Active Statements - var statements = baseActiveStatementsMap.InstructionMap.Values.OrderBy(v => v.Ordinal).ToArray(); + var statements = baseActiveStatementsMap.InstructionMap.Values.OrderBy(v => v.Id.Ordinal).ToArray(); AssertEx.Equal(new[] { $"0: {document1.FilePath}: (9,14)-(9,35) flags=[LeafFrame, MethodUpToDate] mvid=11111111-1111-1111-1111-111111111111 0x06000001 v1 IL_0001", @@ -344,7 +341,7 @@ static void F2() // Active Statements - var baseActiveStatements = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.Ordinal).ToArray(); + var baseActiveStatements = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.Id.Ordinal).ToArray(); AssertEx.Equal(new[] { @@ -526,7 +523,7 @@ static void F4() // Active Statements - var baseActiveStatements = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.Ordinal).ToArray(); + var baseActiveStatements = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.Id.Ordinal).ToArray(); // Note that the spans of AS:2 and AS:3 correspond to the base snapshot (V2). AssertEx.Equal(new[] diff --git a/src/Features/TestUtilities/Diagnostics/MockDiagnosticUpdateSourceRegistrationService.cs b/src/Features/TestUtilities/Diagnostics/MockDiagnosticUpdateSourceRegistrationService.cs deleted file mode 100644 index 73f9dca4b215d..0000000000000 --- a/src/Features/TestUtilities/Diagnostics/MockDiagnosticUpdateSourceRegistrationService.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics -{ - [Export(typeof(IDiagnosticUpdateSourceRegistrationService))] - [Shared] - [PartNotDiscoverable] - internal class MockDiagnosticUpdateSourceRegistrationService : IDiagnosticUpdateSourceRegistrationService - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public MockDiagnosticUpdateSourceRegistrationService() - { - } - - public void Register(IDiagnosticUpdateSource source) - { - // do nothing - } - } -} diff --git a/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs index 7990005d425ea..f5767ebb02d68 100644 --- a/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs +++ b/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -8,19 +8,14 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using Xunit; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.UnitTests.Diagnostics { @@ -37,7 +32,6 @@ public TestDiagnosticAnalyzerDriver(Workspace workspace, bool includeSuppressedD { var mefServices = workspace.Services.SolutionServices.ExportProvider; - Assert.IsType(mefServices.GetExportedValue()); _diagnosticAnalyzerService = Assert.IsType(mefServices.GetExportedValue()); GlobalOptions = mefServices.GetExportedValue(); @@ -61,7 +55,9 @@ private async Task> GetDiagnosticsAsync( if (getDocumentDiagnostics) { var text = await document.GetTextAsync().ConfigureAwait(false); - var dxs = await _diagnosticAnalyzerService.GetDiagnosticsAsync(project.Solution, project.Id, document.Id, _includeSuppressedDiagnostics, _includeNonLocalDocumentDiagnostics, CancellationToken.None); + var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, document.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, + _includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); documentDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync( filterSpan is null ? dxs.Where(d => d.DataLocation.DocumentId != null) @@ -72,7 +68,9 @@ filterSpan is null if (getProjectDiagnostics) { - var dxs = await _diagnosticAnalyzerService.GetDiagnosticsAsync(project.Solution, project.Id, documentId: null, _includeSuppressedDiagnostics, _includeNonLocalDocumentDiagnostics, CancellationToken.None); + var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, + _includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); projectDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync(dxs.Where(d => d.DocumentId is null), project, CancellationToken.None); } diff --git a/src/Features/TestUtilities/EditAndContinue/ActiveStatementTestHelpers.cs b/src/Features/TestUtilities/EditAndContinue/ActiveStatementTestHelpers.cs index bf804d2cd3592..061e9c607936d 100644 --- a/src/Features/TestUtilities/EditAndContinue/ActiveStatementTestHelpers.cs +++ b/src/Features/TestUtilities/EditAndContinue/ActiveStatementTestHelpers.cs @@ -93,7 +93,7 @@ public static string Update(string src, string marker) => InsertNewLines(Delete(src, marker), marker); public static string InspectActiveStatement(ActiveStatement statement) - => $"{statement.Ordinal}: {statement.FileSpan} flags=[{statement.Flags}]"; + => $"{statement.Id.Ordinal}: {statement.FileSpan} flags=[{statement.Flags}]"; public static string InspectActiveStatementAndInstruction(ActiveStatement statement) => InspectActiveStatement(statement) + " " + statement.InstructionId.GetDebuggerDisplay(); diff --git a/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs b/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs index 6aec9fb0ffc27..ec33eac92f782 100644 --- a/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs +++ b/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs @@ -23,7 +23,7 @@ internal class ActiveStatementsDescription public readonly ActiveStatementsMap OldStatementsMap; public readonly ImmutableArray NewMappedSpans; public readonly ImmutableArray> NewMappedRegions; - public readonly ImmutableArray OldUnmappedTrackingSpans; + public readonly ImmutableArray OldUnmappedTrackingSpans; private ActiveStatementsDescription() { @@ -61,12 +61,13 @@ public ActiveStatementsDescription(string oldMarkedSource, string newMarkedSourc newMappedRegions.ZeroInit(activeStatementCount); // initialize with deleted spans (they will retain their file path): - foreach (var oldStatement in OldStatements) + for (var i = 0; i < OldStatements.Length; i++) { + var oldStatement = OldStatements[i]; if (oldStatement.Statement != null) { - newMappedSpans[oldStatement.Statement.Ordinal] = new SourceFileSpan(oldStatement.Statement.FilePath, default); - newMappedRegions[oldStatement.Statement.Ordinal] = []; + newMappedSpans[i] = new SourceFileSpan(oldStatement.Statement.FilePath, default); + newMappedRegions[i] = []; } } @@ -86,8 +87,8 @@ public ActiveStatementsDescription(string oldMarkedSource, string newMarkedSourc // edits the source and we get their positions when analyzing the new source. // The EnC analyzer uses old tracking spans as hints to find matching nodes. var newText = newTree.GetText(); - OldUnmappedTrackingSpans = SourceMarkers.GetTrackingSpans(newMarkedSource, activeStatementCount). - SelectAsArray(s => newText.Lines.GetLinePositionSpan(s)); + OldUnmappedTrackingSpans = SourceMarkers.GetTrackingSpans(newMarkedSource). + SelectAsArray(s => new ActiveStatementLineSpan(new ActiveStatementId(s.id), newText.Lines.GetLinePositionSpan(s.span))); } internal static ImmutableArray CreateActiveStatementMapFromMarkers( @@ -99,7 +100,7 @@ internal static ImmutableArray CreateActiveStatementMap var activeStatementMarkers = SourceMarkers.GetActiveSpans(markedSource).ToArray(); var exceptionRegionMarkers = SourceMarkers.GetExceptionRegions(markedSource); - return activeStatementMarkers.Aggregate( + return [.. activeStatementMarkers.Aggregate( new List(), (list, marker) => { @@ -117,7 +118,7 @@ internal static ImmutableArray CreateActiveStatementMap var unmappedActiveStatement = new UnmappedActiveStatement( unmappedSpan, new ActiveStatement( - ordinal, + new ActiveStatementId(ordinal), statementFlags, mappedSpan, instructionId: default), @@ -125,7 +126,7 @@ internal static ImmutableArray CreateActiveStatementMap documentActiveStatements.Add(unmappedActiveStatement.Statement); return SourceMarkers.SetListItem(list, ordinal, unmappedActiveStatement); - }).ToImmutableArray(); + })]; } internal static ImmutableArray GetUnmappedActiveStatements( @@ -150,8 +151,8 @@ internal static ImmutableArray GetUnmappedActiveStateme sourceIndex++; } - activeStatements.Sort((x, y) => x.Statement.Ordinal.CompareTo(y.Statement.Ordinal)); - return activeStatements.ToImmutable(); + activeStatements.Sort((x, y) => x.Statement.Id.Ordinal.CompareTo(y.Statement.Id.Ordinal)); + return activeStatements.ToImmutableAndClear(); } internal static ImmutableArray GetActiveStatementDebugInfos( @@ -167,11 +168,11 @@ internal static ImmutableArray GetActiveStateme new ManagedActiveStatementDebugInfo( new ManagedInstructionId( new ManagedMethodId( - (modules != null) ? modules[statement.Ordinal] : moduleId, + (modules != null) ? modules[statement.Id.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), + token: 0x06000000 | (methodRowIds != null ? methodRowIds[statement.Id.Ordinal] : statement.Id.Ordinal + 1), + version: (methodVersions != null) ? methodVersions[statement.Id.Ordinal] : 1)), + ilOffset: (ilOffsets != null) ? ilOffsets[statement.Id.Ordinal] : 0), documentName: statement.FilePath, sourceSpan: statement.Span.ToSourceSpan(), flags: statement.Flags)); diff --git a/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index 2f6ad1f92116a..df6d379295bcf 100644 --- a/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -31,7 +31,8 @@ internal abstract class EditAndContinueTestHelpers EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddStaticFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType | - EditAndContinueCapabilities.NewTypeDefinition; + EditAndContinueCapabilities.NewTypeDefinition | + EditAndContinueCapabilities.AddExplicitInterfaceImplementation; public const EditAndContinueCapabilities Net6RuntimeCapabilities = Net5RuntimeCapabilities | @@ -60,7 +61,7 @@ private void VerifyDocumentActiveStatementsAndExceptionRegions( ImmutableArray> actualNewExceptionRegions) { // check active statements: - AssertSpansEqual(description.NewMappedSpans, actualNewActiveStatements.OrderBy(x => x.Ordinal).Select(s => s.FileSpan), newTree); + AssertSpansEqual(description.NewMappedSpans, actualNewActiveStatements.OrderBy(x => x.Id.Ordinal).Select(s => s.FileSpan), newTree); var oldRoot = oldTree.GetRoot(); @@ -84,7 +85,7 @@ private void VerifyDocumentActiveStatementsAndExceptionRegions( for (var i = 0; i < actualNewActiveStatements.Length; i++) { var activeStatement = actualNewActiveStatements[i]; - AssertSpansEqual(description.NewMappedRegions[activeStatement.Ordinal], actualNewExceptionRegions[i], newTree); + AssertSpansEqual(description.NewMappedRegions[activeStatement.Id.Ordinal], actualNewExceptionRegions[i], newTree); } } } diff --git a/src/Features/TestUtilities/EditAndContinue/MockActiveStatementSpanProvider.cs b/src/Features/TestUtilities/EditAndContinue/MockActiveStatementSpanProvider.cs index d986e9a225a3b..fa4e8362a2c34 100644 --- a/src/Features/TestUtilities/EditAndContinue/MockActiveStatementSpanProvider.cs +++ b/src/Features/TestUtilities/EditAndContinue/MockActiveStatementSpanProvider.cs @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { - internal class MockActiveStatementSpanProvider : IActiveStatementSpanProvider + internal class MockActiveStatementSpanProvider : IActiveStatementSpanFactory { public Func, ImmutableArray>>? GetBaseActiveStatementSpansImpl; public Func>? GetAdjustedActiveStatementSpansImpl; diff --git a/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs b/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs similarity index 74% rename from src/Features/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs rename to src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs index 028cb6e2d650b..2128da2192701 100644 --- a/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs +++ b/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs @@ -7,48 +7,34 @@ using System.Composition; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { - internal delegate void ActionOut(out TArg1 arg); - internal delegate void ActionOut(TArg1 arg1, out TArg2 arg2); - [Export(typeof(IEditAndContinueService)), Shared] - internal class MockEditAndContinueWorkspaceService : IEditAndContinueService + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal class MockEditAndContinueService() : IEditAndContinueService { public Func, ImmutableArray>>? GetBaseActiveStatementSpansImpl; public Func>? GetAdjustedActiveStatementSpansImpl; public Func, bool, bool, DebuggingSessionId>? StartDebuggingSessionImpl; - public ActionOut>? EndDebuggingSessionImpl; + public Action? EndDebuggingSessionImpl; public Func? EmitSolutionUpdateImpl; public Action? OnSourceFileUpdatedImpl; - public ActionOut>? CommitSolutionUpdateImpl; - public ActionOut>? BreakStateOrCapabilitiesChangedImpl; + public Action? CommitSolutionUpdateImpl; + public Action? BreakStateOrCapabilitiesChangedImpl; public Action? DiscardSolutionUpdateImpl; public Func>? GetDocumentDiagnosticsImpl; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public MockEditAndContinueWorkspaceService() - { - } - - public void BreakStateOrCapabilitiesChanged(DebuggingSessionId sessionId, bool? inBreakState, out ImmutableArray documentsToReanalyze) - { - documentsToReanalyze = []; - BreakStateOrCapabilitiesChangedImpl?.Invoke(inBreakState, out documentsToReanalyze); - } + public void BreakStateOrCapabilitiesChanged(DebuggingSessionId sessionId, bool? inBreakState) + => BreakStateOrCapabilitiesChangedImpl?.Invoke(inBreakState); - public void CommitSolutionUpdate(DebuggingSessionId sessionId, out ImmutableArray documentsToReanalyze) - { - documentsToReanalyze = []; - CommitSolutionUpdateImpl?.Invoke(out documentsToReanalyze); - } + public void CommitSolutionUpdate(DebuggingSessionId sessionId) + => CommitSolutionUpdateImpl?.Invoke(); public void DiscardSolutionUpdate(DebuggingSessionId sessionId) => DiscardSolutionUpdateImpl?.Invoke(); @@ -56,11 +42,8 @@ public void DiscardSolutionUpdate(DebuggingSessionId sessionId) public ValueTask EmitSolutionUpdateAsync(DebuggingSessionId sessionId, Solution solution, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) => new((EmitSolutionUpdateImpl ?? throw new NotImplementedException()).Invoke(solution, activeStatementSpanProvider)); - public void EndDebuggingSession(DebuggingSessionId sessionId, out ImmutableArray documentsToReanalyze) - { - documentsToReanalyze = []; - EndDebuggingSessionImpl?.Invoke(out documentsToReanalyze); - } + public void EndDebuggingSession(DebuggingSessionId sessionId) + => EndDebuggingSessionImpl?.Invoke(); public ValueTask>> GetBaseActiveStatementSpansAsync(DebuggingSessionId sessionId, Solution solution, ImmutableArray documentIds, CancellationToken cancellationToken) => new((GetBaseActiveStatementSpansImpl ?? throw new NotImplementedException()).Invoke(solution, documentIds)); diff --git a/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs b/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs index 5856ad991a36e..1c43208de1796 100644 --- a/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs +++ b/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs @@ -85,7 +85,7 @@ internal static string[] Clear(string[] sources) public static IEnumerable<(TextSpan Span, int Id)> GetActiveSpans(string markedSource) => GetSpans(markedSource, tagName: "AS").Select(s => (s.span, s.id.major)); - public static TextSpan[] GetTrackingSpans(string src, int count) + public static (int id, TextSpan span)[] GetTrackingSpans(string src) { var matches = s_trackingStatementPattern.Matches(src); if (matches.Count == 0) @@ -93,20 +93,20 @@ public static TextSpan[] GetTrackingSpans(string src, int count) return []; } - var result = new TextSpan[count]; + var result = new List<(int id, TextSpan span)>(); for (var i = 0; i < matches.Count; i++) { var span = matches[i].Groups["TrackingStatement"]; foreach (var (id, _) in ParseIds(matches[i])) { - result[id] = new TextSpan(span.Index, span.Length); + result.Add((id, new TextSpan(span.Index, span.Length))); } } Contract.ThrowIfTrue(result.Any(span => span == default)); - return result; + return [.. result]; } public static ImmutableArray> GetExceptionRegions(string markedSource) diff --git a/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj b/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj index 97bbe469279d1..cc5520dcabc29 100644 --- a/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj +++ b/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj @@ -36,6 +36,7 @@ + diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb index 220fea7817f55..206082535f804 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb @@ -9,7 +9,6 @@ Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Completion.Providers Imports Microsoft.CodeAnalysis.ErrorReporting Imports Microsoft.CodeAnalysis.Host.Mef -Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Utilities.DocumentationCommentXmlNames @@ -27,6 +26,18 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers MyBase.New(s_defaultRules) End Sub + Private Shared ReadOnly s_keywordNames As ImmutableArray(Of String) + + Shared Sub New() + Dim keywordsBuilder As New List(Of String) + + For Each keywordKind In SyntaxFacts.GetKeywordKinds() + keywordsBuilder.Add(SyntaxFacts.GetText(keywordKind)) + Next + + s_keywordNames = keywordsBuilder.ToImmutableArray() + End Sub + Friend Overrides ReadOnly Property Language As String Get Return LanguageNames.VisualBasic @@ -270,16 +281,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers End If End Sub - Protected Overrides Iterator Function GetKeywordNames() As IEnumerable(Of String) - Yield SyntaxFacts.GetText(SyntaxKind.NothingKeyword) - Yield SyntaxFacts.GetText(SyntaxKind.SharedKeyword) - Yield SyntaxFacts.GetText(SyntaxKind.OverridableKeyword) - Yield SyntaxFacts.GetText(SyntaxKind.TrueKeyword) - Yield SyntaxFacts.GetText(SyntaxKind.FalseKeyword) - Yield SyntaxFacts.GetText(SyntaxKind.MustInheritKeyword) - Yield SyntaxFacts.GetText(SyntaxKind.NotOverridableKeyword) - Yield SyntaxFacts.GetText(SyntaxKind.AsyncKeyword) - Yield SyntaxFacts.GetText(SyntaxKind.AwaitKeyword) + Protected Overrides Function GetKeywordNames() As ImmutableArray(Of String) + Return s_keywordNames End Function Protected Overrides Function GetExistingTopLevelElementNames(parentTrivia As DocumentationCommentTriviaSyntax) As IEnumerable(Of String) diff --git a/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb b/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb index 77f2abd72d4b7..695f312588b15 100644 --- a/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb +++ b/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb @@ -197,7 +197,7 @@ 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(services, semanticModel, token, New TokenInformation(types), supportedPlatforms:=Nothing, options, cancellationToken).ConfigureAwait(False) + Return Await CreateContentAsync(services, semanticModel, token, New TokenInformation(types), supportedPlatforms:=Nothing, options, onTheFlyDocsElement:=Nothing, cancellationToken).ConfigureAwait(False) End Function Private Shared Function BuildContentForIntrinsicOperator( diff --git a/src/Features/VisualBasicTest/Diagnostics/Suppression/SuppressionAllCodeTests.vb b/src/Features/VisualBasicTest/Diagnostics/Suppression/SuppressionAllCodeTests.vb index 99cf92d7ad2a7..3d5e494cae508 100644 --- a/src/Features/VisualBasicTest/Diagnostics/Suppression/SuppressionAllCodeTests.vb +++ b/src/Features/VisualBasicTest/Diagnostics/Suppression/SuppressionAllCodeTests.vb @@ -17,9 +17,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.Suppre Inherits AbstractSuppressionAllCodeTests Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = FeaturesTestCompositions.Features _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ - .AddParts(GetType(MockDiagnosticUpdateSourceRegistrationService)) _ - .AddAssemblies(GetType(DiagnosticService).Assembly) + .AddAssemblies(GetType(DiagnosticAnalyzerService).Assembly) Protected Overrides Function CreateWorkspaceFromFile(definition As String, parseOptions As ParseOptions) As TestWorkspace Return TestWorkspace.CreateVisualBasic(definition, DirectCast(parseOptions, VisualBasicParseOptions), composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) diff --git a/src/Features/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/Features/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index 95b726d40e429..e00b01e5dd200 100644 --- a/src/Features/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/Features/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -5137,7 +5137,7 @@ End Class edits.VerifySemanticDiagnostics( active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub @@ -5167,7 +5167,7 @@ End Class edits.VerifySemanticDiagnostics( active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub @@ -5284,7 +5284,7 @@ End Class edits.VerifySemanticDiagnostics( active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub @@ -5403,7 +5403,7 @@ End Class edits.VerifySemanticDiagnostics( active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub @@ -5432,7 +5432,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifySemanticDiagnostics(active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub @@ -5515,7 +5515,7 @@ End Class ' No rude edit since the AS is within the nested function. edits.VerifySemanticDiagnostics(active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub diff --git a/src/Features/VisualBasicTest/EditAndContinue/StatementEditingTests.vb b/src/Features/VisualBasicTest/EditAndContinue/StatementEditingTests.vb index d31fbfda9c867..a6550aca7c311 100644 --- a/src/Features/VisualBasicTest/EditAndContinue/StatementEditingTests.vb +++ b/src/Features/VisualBasicTest/EditAndContinue/StatementEditingTests.vb @@ -5309,7 +5309,7 @@ End Class VerifySemanticDiagnostics( editScript:=edits, targetFrameworks:={TargetFramework.Mscorlib40AndSystemCore}, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub #End Region @@ -5419,7 +5419,7 @@ End Class VerifySemanticDiagnostics( edits, targetFrameworks:={TargetFramework.MinimalAsync}, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub diff --git a/src/Features/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/Features/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 3eaf69d316e74..b1dd74eafa024 100644 --- a/src/Features/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/Features/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -4789,6 +4789,10 @@ End Structure "Update [Function F() As Task(Of String)]@11 -> [Async Function F() As Task(Of String)]@11") edits.VerifySemanticDiagnostics( + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) + + edits.VerifySemanticDiagnostics( + {Diagnostic(RudeEditKind.MakeMethodAsyncNotSupportedByRuntime, "Async Function F()")}, capabilities:=EditAndContinueCapabilities.NewTypeDefinition) End Sub @@ -5268,7 +5272,7 @@ End Class" "Update [Function F() As Task(Of String)]@11 -> [Iterator Function F() As Task(Of String)]@11") edits.VerifySemanticDiagnostics( - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub diff --git a/src/Features/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb b/src/Features/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb index d4ec67748c94b..e6af37ce7b3a3 100644 --- a/src/Features/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb +++ b/src/Features/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb @@ -123,7 +123,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Dim analyzer = New VisualBasicEditAndContinueAnalyzer() Dim baseActiveStatements = AsyncLazy.Create(If(activeStatementMap, ActiveStatementsMap.Empty)) Dim capabilities = AsyncLazy.Create(EditAndContinueTestHelpers.Net5RuntimeCapabilities) - Return Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray(Of LinePositionSpan).Empty, capabilities, CancellationToken.None) + Return Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray(Of ActiveStatementLineSpan).Empty, capabilities, CancellationToken.None) End Function #End Region @@ -485,7 +485,7 @@ End Class { KeyValuePairUtil.Create(newDocument.FilePath, ImmutableArray.Create( New ActiveStatement( - ordinal:=0, + New ActiveStatementId(0), ActiveStatementFlags.LeafFrame, New SourceFileSpan(newDocument.FilePath, oldStatementSpan), instructionId:=Nothing))) diff --git a/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj b/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj index 49a4b6546224f..d62e1c2961bef 100644 --- a/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj +++ b/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj @@ -2,7 +2,8 @@ net472 - true + + true false diff --git a/src/Scripting/CSharpTest/InteractiveSessionTests.cs b/src/Scripting/CSharpTest/InteractiveSessionTests.cs index 10c6016032737..24e7362698c70 100644 --- a/src/Scripting/CSharpTest/InteractiveSessionTests.cs +++ b/src/Scripting/CSharpTest/InteractiveSessionTests.cs @@ -1215,7 +1215,7 @@ public interface I public class C : I { public int F() => 1; -}", new MetadataReference[] { NetStandard13.SystemRuntime, lib1.ToMetadataReference() }); +}", new MetadataReference[] { NetStandard13.References.SystemRuntime, lib1.ToMetadataReference() }); lib2.Emit(file2.Path); @@ -1277,7 +1277,7 @@ public void ExtensionPriority1() var main = CreateCSharpCompilation( @"public static class M { public static readonly C X = new C(); }", - new MetadataReference[] { NetStandard13.SystemRuntime, libExe.ToMetadataReference() }, + new MetadataReference[] { NetStandard13.References.SystemRuntime, libExe.ToMetadataReference() }, mainName); var exeImage = libExe.EmitToArray(); @@ -1307,7 +1307,7 @@ public void ExtensionPriority2() var main = CreateCSharpCompilation( @"public static class M { public static readonly C X = new C(); }", - new MetadataReference[] { NetStandard13.SystemRuntime, libExe.ToMetadataReference() }, + new MetadataReference[] { NetStandard13.References.SystemRuntime, libExe.ToMetadataReference() }, mainName); var exeImage = libExe.EmitToArray(); diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.FormattedMember.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.FormattedMember.cs index a3f72c861bf9b..8bce8213047d9 100644 --- a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.FormattedMember.cs +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.FormattedMember.cs @@ -48,7 +48,7 @@ public string GetDisplayName() public bool HasKeyName() { - return Index >= 0 && Name != null && Name.Length >= 2 && Name[0] == '[' && Name[Name.Length - 1] == ']'; + return Index >= 0 && Name != null && Name.Length >= 2 && Name[0] == '[' && Name[^1] == ']'; } public bool AppendAsCollectionEntry(Builder result) diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs index 5c90553b23d48..2303b85fedd59 100644 --- a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs @@ -697,7 +697,7 @@ private void FormatMultidimensionalArrayElements(Builder result, Array array, bo string _; FormatObjectRecursive(result, array.GetValue(indices), isRoot: false, debuggerDisplayName: out _); - indices[indices.Length - 1]++; + indices[^1]++; flatIndex++; } } diff --git a/src/Scripting/CoreTestUtilities/TestCompilationFactory.cs b/src/Scripting/CoreTestUtilities/TestCompilationFactory.cs index b48f3ddb82ca7..eb729b278204d 100644 --- a/src/Scripting/CoreTestUtilities/TestCompilationFactory.cs +++ b/src/Scripting/CoreTestUtilities/TestCompilationFactory.cs @@ -22,7 +22,7 @@ internal static Compilation CreateCSharpCompilationWithCorlib(string source, str return CSharpCompilation.Create( assemblyName ?? Guid.NewGuid().ToString(), new[] { CSharp.SyntaxFactory.ParseSyntaxTree(SourceText.From(source, encoding: null, SourceHashAlgorithms.Default)) }, - new[] { NetStandard13.SystemRuntime }, + new[] { NetStandard13.References.SystemRuntime }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); } @@ -31,7 +31,7 @@ internal static Compilation CreateVisualBasicCompilationWithCorlib(string source return VisualBasicCompilation.Create( assemblyName ?? Guid.NewGuid().ToString(), new[] { VisualBasic.SyntaxFactory.ParseSyntaxTree(SourceText.From(source, encoding: null, SourceHashAlgorithms.Default)) }, - new[] { NetStandard13.SystemRuntime }, + new[] { NetStandard13.References.SystemRuntime }, new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); } diff --git a/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.Desktop.Config.csproj b/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.Desktop.Config.csproj deleted file mode 100644 index 51fa8dabdbd53..0000000000000 --- a/src/Setup/DevDivVsix/ServiceHubConfig/Roslyn.VisualStudio.Setup.ServiceHub.Desktop.Config.csproj +++ /dev/null @@ -1,62 +0,0 @@ - - - - - net472 - - - Roslyn.VisualStudio.Setup.ServiceHub.Desktop.Config.vsix - Microsoft.CodeAnalysis.LanguageServices - - - - ..\..\..\IDE\$(CommonExtensionInstallationRoot)\$(LanguageServicesExtensionInstallationFolder)\ - desktop - - - - - <_SwrFilePath>$(IntermediateOutputPath)Roslyn.VisualStudio.Setup.ServiceHub.Desktop.Config.swr - - - - - - - - <_ServiceHubConfigFiles Include="@(ServiceHubService)" FileSuffix="64" /> - <_ServiceHubConfigFiles Include="@(ServiceHubService)" FileSuffix="64S" /> - <_FileEntries Include='file source="$(IntermediateOutputPath)%(_ServiceHubConfigFiles.Identity)%(_ServiceHubConfigFiles.FileSuffix).servicehub.service.json"'/> - - - - <_Lines> - - - - - - - - - - - - - - diff --git a/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs b/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs index 400df749f61f7..a501677df6270 100644 --- a/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs +++ b/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs @@ -53,7 +53,7 @@ public async Task RunAsync(CancellationToken cancellationToken) if (usePersistentStorage) { var persistentStorageService = _workspace.Services.SolutionServices.GetPersistentStorageService(); - await using var persistentStorage = await persistentStorageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), cancellationToken).ConfigureAwait(false); + var persistentStorage = await persistentStorageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), cancellationToken).ConfigureAwait(false); if (persistentStorage is NoOpPersistentStorage) { throw new InvalidOperationException("Benchmark is not configured to use persistent storage."); diff --git a/src/Tools/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotAnalyzer.cs b/src/Tools/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs similarity index 83% rename from src/Tools/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotAnalyzer.cs rename to src/Tools/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs index c5e36413a3e02..e2e5d1d11dffb 100644 --- a/src/Tools/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotAnalyzer.cs +++ b/src/Tools/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs @@ -5,12 +5,11 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot; -internal interface IExternalCopilotCodeAnalysisService : ILanguageService +internal interface IExternalCSharpCopilotCodeAnalysisService { // mirror the ICopilotCodeAnalysisService interface Task IsAvailableAsync(CancellationToken cancellation); @@ -18,4 +17,5 @@ internal interface IExternalCopilotCodeAnalysisService : ILanguageService Task> AnalyzeDocumentAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken); Task> GetCachedDiagnosticsAsync(Document document, string promptTitle, CancellationToken cancellationToken); Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken); + Task GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken); } diff --git a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs index 140b63cf0eaf2..0f65dd06ac618 100644 --- a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs +++ b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.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.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.Immutable; using System.Runtime.CompilerServices; using System.Threading; @@ -24,35 +24,42 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.Analyzer; /// Additionally, it performs all the option checks and Copilot service availability checks /// to determine if we should skip analysis or not. /// -internal abstract class AbstractCopilotCodeAnalysisService( - Lazy lazyExternalCopilotService, - IDiagnosticsRefresher diagnosticsRefresher) : ICopilotCodeAnalysisService +internal abstract class AbstractCopilotCodeAnalysisService(IDiagnosticsRefresher diagnosticsRefresher) : ICopilotCodeAnalysisService { // The _diagnosticsCache is a cache for computed diagnostics via `AnalyzeDocumentAsync`. // Each document maps to a dictionary, which in tern maps a prompt title to a list of existing Diagnostics and a boolean flag. - // The list of diangostics represents the diagnostics computed for the document under the given prompt title, + // The list of diagnostics represents the diagnostics computed for the document under the given prompt title, // the boolean flag indicates whether the diagnostics result is for the entire document. // This cache is used to avoid duplicate analysis calls by storing the computed diagnostics for each document and prompt title. private readonly ConditionalWeakTable Diagnostics, bool IsCompleteResult)>> _diagnosticsCache = new(); - public abstract Task IsRefineOptionEnabledAsync(); - - public abstract Task IsCodeAnalysisOptionEnabledAsync(); + protected abstract Task IsAvailableCoreAsync(CancellationToken cancellationToken); + protected abstract Task> GetAvailablePromptTitlesCoreAsync(Document document, CancellationToken cancellationToken); + protected abstract Task> AnalyzeDocumentCoreAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken); + protected abstract Task> GetCachedDiagnosticsCoreAsync(Document document, string promptTitle, CancellationToken cancellationToken); + protected abstract Task StartRefinementSessionCoreAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken); + protected abstract Task GetOnTheFlyDocsCoreAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken); public Task IsAvailableAsync(CancellationToken cancellationToken) - => lazyExternalCopilotService.Value.IsAvailableAsync(cancellationToken); + => IsAvailableCoreAsync(cancellationToken); public async Task> GetAvailablePromptTitlesAsync(Document document, CancellationToken cancellationToken) { - if (!await IsCodeAnalysisOptionEnabledAsync().ConfigureAwait(false)) + if (document.GetLanguageService() is not { } service) + return []; + + if (!await service.IsCodeAnalysisOptionEnabledAsync().ConfigureAwait(false)) return []; - return await lazyExternalCopilotService.Value.GetAvailablePromptTitlesAsync(document, cancellationToken).ConfigureAwait(false); + return await GetAvailablePromptTitlesCoreAsync(document, cancellationToken).ConfigureAwait(false); } private async Task ShouldSkipAnalysisAsync(Document document, CancellationToken cancellationToken) { - if (!await IsCodeAnalysisOptionEnabledAsync().ConfigureAwait(false)) + if (document.GetLanguageService() is not { } service) + return true; + + if (!await service.IsCodeAnalysisOptionEnabledAsync().ConfigureAwait(false)) return true; if (await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false)) @@ -73,7 +80,7 @@ public async Task AnalyzeDocumentAsync(Document document, TextSpan? span, string return; var isFullDocumentAnalysis = !span.HasValue; - var diagnostics = await lazyExternalCopilotService.Value.AnalyzeDocumentAsync(document, span, promptTitle, cancellationToken).ConfigureAwait(false); + var diagnostics = await AnalyzeDocumentCoreAsync(document, span, promptTitle, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); @@ -116,7 +123,7 @@ private void CacheAndRefreshDiagnosticsIfNeeded(Document document, string prompt diagnosticsRefresher.RequestWorkspaceRefresh(); } - public async Task> GetCachedDocumentDiagnosticsAsync(Document document, ImmutableArray promptTitles, CancellationToken cancellationToken) + public async Task> GetCachedDocumentDiagnosticsAsync(Document document, TextSpan? span, ImmutableArray promptTitles, CancellationToken cancellationToken) { if (await ShouldSkipAnalysisAsync(document, cancellationToken).ConfigureAwait(false)) return []; @@ -138,18 +145,37 @@ public async Task> GetCachedDocumentDiagnosticsAsync( } else { - var cachedDiagnostics = await lazyExternalCopilotService.Value.GetCachedDiagnosticsAsync(document, promptTitle, cancellationToken).ConfigureAwait(false); + var cachedDiagnostics = await GetCachedDiagnosticsCoreAsync(document, promptTitle, cancellationToken).ConfigureAwait(false); diagnostics.AddRange(cachedDiagnostics); CacheAndRefreshDiagnosticsIfNeeded(document, promptTitle, cachedDiagnostics, isCompleteResult: false); } } + if (span.HasValue) + return await GetDiagnosticsIntersectWithSpanAsync(document, diagnostics, span.Value, cancellationToken).ConfigureAwait(false); + return diagnostics.ToImmutable(); } + protected virtual Task> GetDiagnosticsIntersectWithSpanAsync(Document document, IReadOnlyList diagnostics, TextSpan span, CancellationToken cancellationToken) + { + return Task.FromResult(diagnostics.WhereAsArray((diagnostic, _) => diagnostic.Location.SourceSpan.IntersectsWith(span), state: (object)null)); + } + public async Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken) { - if (await IsRefineOptionEnabledAsync().ConfigureAwait(false)) - await lazyExternalCopilotService.Value.StartRefinementSessionAsync(oldDocument, newDocument, primaryDiagnostic, cancellationToken).ConfigureAwait(false); + if (oldDocument.GetLanguageService() is not { } service) + return; + + if (await service.IsRefineOptionEnabledAsync().ConfigureAwait(false)) + await StartRefinementSessionCoreAsync(oldDocument, newDocument, primaryDiagnostic, cancellationToken).ConfigureAwait(false); + } + + public async Task GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken) + { + if (!await IsAvailableAsync(cancellationToken).ConfigureAwait(false)) + return string.Empty; + + return await GetOnTheFlyDocsCoreAsync(symbolSignature, declarationCode, language, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs index 987c73f48c0ca..425f5366bf931 100644 --- a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs +++ b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs @@ -19,11 +19,12 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.Analyzer.CSharp using GetCachedDiagnosticsAsyncDelegateType = Func>>; using IsAvailableAsyncDelegateType = Func>; using StartRefinementSessionAsyncDelegateType = Func; +using GetOnTheFlyDocsAsyncDelegateType = Func, string, CancellationToken, Task>; internal sealed partial class CSharpCopilotCodeAnalysisService { - // A temporary helper to get access to the implementation of IExternalCopilotCodeAnalysisService, until it can be MEF exported. - private sealed class ReflectionWrapper : IExternalCopilotCodeAnalysisService + // A temporary helper to get access to the implementation of IExternalCSharpCopilotCodeAnalysisService, until it can be MEF exported. + private sealed class ReflectionWrapper : IExternalCSharpCopilotCodeAnalysisService { private const string CopilotRoslynDllName = "Microsoft.VisualStudio.Copilot.Roslyn, Version=0.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; private const string InternalCSharpCopilotAnalyzerTypeFullName = "Microsoft.VisualStudio.Copilot.Roslyn.Analyzer.InternalCSharpCopilotAnalyzer"; @@ -33,6 +34,7 @@ private sealed class ReflectionWrapper : IExternalCopilotCodeAnalysisService private const string AnalyzeDocumentAsyncMethodName = "AnalyzeDocumentAsync"; private const string GetCachedDiagnosticsAsyncMethodName = "GetCachedDiagnosticsAsync"; private const string StartRefinementSessionAsyncMethodName = "StartRefinementSessionAsync"; + private const string GetOnTheFlyDocsAsyncMethodName = "GetOnTheFlyDocsAsync"; // Create and cache closed delegate to ensure we use a singleton object and with better performance. private readonly Type? _analyzerType; @@ -42,6 +44,7 @@ private sealed class ReflectionWrapper : IExternalCopilotCodeAnalysisService private readonly Lazy _lazyAnalyzeDocumentAsyncDelegate; private readonly Lazy _lazyGetCachedDiagnosticsAsyncDelegate; private readonly Lazy _lazyStartRefinementSessionAsyncDelegate; + private readonly Lazy _lazyGetOnTheFlyDocsAsyncDelegate; public ReflectionWrapper(IServiceProvider serviceProvider, IVsService brokeredServiceContainer) { @@ -69,6 +72,7 @@ public ReflectionWrapper(IServiceProvider serviceProvider, IVsService(string methodName, Type[] types) where T : Delegate @@ -104,6 +108,9 @@ public ReflectionWrapper(IServiceProvider serviceProvider, IVsService CreateDelegate(StartRefinementSessionAsyncMethodName, [typeof(Document), typeof(Document), typeof(Diagnostic), typeof(CancellationToken)]); + private GetOnTheFlyDocsAsyncDelegateType? CreateGetOnTheFlyDocsAsyncDelegate() + => CreateDelegate(GetOnTheFlyDocsAsyncMethodName, [typeof(string), typeof(ImmutableArray), typeof(string), typeof(CancellationToken)]); + public async Task IsAvailableAsync(CancellationToken cancellationToken) { if (_lazyIsAvailableAsyncDelegate.Value is null) @@ -143,5 +150,13 @@ public Task StartRefinementSessionAsync(Document oldDocument, Document newDocume return _lazyStartRefinementSessionAsyncDelegate.Value(oldDocument, newDocument, primaryDiagnostic, cancellationToken); } + + public async Task GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken) + { + if (_lazyGetOnTheFlyDocsAsyncDelegate.Value is null) + return string.Empty; + + return await _lazyGetOnTheFlyDocsAsyncDelegate.Value(symbolSignature, declarationCode, language, cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs index 3d930e1f562bf..e9f1006c59e01 100644 --- a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs +++ b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs @@ -3,41 +3,80 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.ServiceBroker; namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.Analyzer.CSharp; -internal sealed partial class CSharpCopilotCodeAnalysisService( - Lazy lazyExternalCopilotService, - IDiagnosticsRefresher diagnosticsRefresher, - VisualStudioCopilotOptionService copilotOptionService) : AbstractCopilotCodeAnalysisService(lazyExternalCopilotService, diagnosticsRefresher) +[ExportLanguageService(typeof(ICopilotCodeAnalysisService), LanguageNames.CSharp), Shared] +internal sealed partial class CSharpCopilotCodeAnalysisService : AbstractCopilotCodeAnalysisService { - private const string CopilotRefineOptionName = "EnableCSharpRefineQuickActionSuggestion"; - private const string CopilotCodeAnalysisOptionName = "EnableCSharpCodeAnalysis"; + private readonly Lazy _lazyExternalCopilotService; - public static CSharpCopilotCodeAnalysisService Create( - HostLanguageServices languageServices, + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpCopilotCodeAnalysisService( + [Import(AllowDefault = true)] IExternalCSharpCopilotCodeAnalysisService? externalCopilotService, IDiagnosticsRefresher diagnosticsRefresher, - VisualStudioCopilotOptionService copilotOptionService, SVsServiceProvider serviceProvider, - IVsService brokeredServiceContainer) + IVsService brokeredServiceContainer + ) : base(diagnosticsRefresher) { - var lazyExternalCopilotService = new Lazy(GetExternalService, LazyThreadSafetyMode.PublicationOnly); - return new CSharpCopilotCodeAnalysisService(lazyExternalCopilotService, diagnosticsRefresher, copilotOptionService); + _lazyExternalCopilotService = new Lazy(GetExternalService, LazyThreadSafetyMode.PublicationOnly); - IExternalCopilotCodeAnalysisService GetExternalService() - => languageServices.GetService() ?? new ReflectionWrapper(serviceProvider, brokeredServiceContainer); + IExternalCSharpCopilotCodeAnalysisService GetExternalService() + => externalCopilotService ?? new ReflectionWrapper(serviceProvider, brokeredServiceContainer); } - public override Task IsRefineOptionEnabledAsync() - => copilotOptionService.IsCopilotOptionEnabledAsync(CopilotRefineOptionName); + protected override Task> AnalyzeDocumentCoreAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken) + => _lazyExternalCopilotService.Value.AnalyzeDocumentAsync(document, span, promptTitle, cancellationToken); - public override Task IsCodeAnalysisOptionEnabledAsync() - => copilotOptionService.IsCopilotOptionEnabledAsync(CopilotCodeAnalysisOptionName); + protected override Task> GetAvailablePromptTitlesCoreAsync(Document document, CancellationToken cancellationToken) + => _lazyExternalCopilotService.Value.GetAvailablePromptTitlesAsync(document, cancellationToken); + + protected override Task> GetCachedDiagnosticsCoreAsync(Document document, string promptTitle, CancellationToken cancellationToken) + => _lazyExternalCopilotService.Value.GetCachedDiagnosticsAsync(document, promptTitle, cancellationToken); + + protected override Task IsAvailableCoreAsync(CancellationToken cancellationToken) + => _lazyExternalCopilotService.Value.IsAvailableAsync(cancellationToken); + + protected override Task StartRefinementSessionCoreAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken) + => _lazyExternalCopilotService.Value.StartRefinementSessionAsync(oldDocument, newDocument, primaryDiagnostic, cancellationToken); + + protected override Task GetOnTheFlyDocsCoreAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken) + => _lazyExternalCopilotService.Value.GetOnTheFlyDocsAsync(symbolSignature, declarationCode, language, cancellationToken); + + protected override async Task> GetDiagnosticsIntersectWithSpanAsync( + Document document, IReadOnlyList diagnostics, TextSpan span, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var filteredDiagnostics); + + // The location of Copilot diagnostics is on the method identifier, we'd like to expand the range to include them + // if any part of the method intersects with the given span. + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + + foreach (var diagnostic in diagnostics) + { + var containingMethod = syntaxFacts.GetContainingMethodDeclaration(root, diagnostic.Location.SourceSpan.Start, useFullSpan: false); + if (containingMethod?.Span.IntersectsWith(span) is true) + filteredDiagnostics.Add(diagnostic); + } + + return filteredDiagnostics.ToImmutable(); + } } diff --git a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisServiceFactory.cs b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisServiceFactory.cs deleted file mode 100644 index 8a9e3bc248540..0000000000000 --- a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisServiceFactory.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Copilot; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.ServiceBroker; - -namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.Analyzer.CSharp; - -[ExportLanguageServiceFactory(typeof(ICopilotCodeAnalysisService), LanguageNames.CSharp), Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed partial class CSharpCopilotCodeAnalysisServiceFactory( - IDiagnosticsRefresher diagnosticsRefresher, - VisualStudioCopilotOptionService copilotOptionService, - SVsServiceProvider serviceProvider, - IVsService brokeredServiceContainer) : ILanguageServiceFactory -{ - public ILanguageService CreateLanguageService(HostLanguageServices languageServices) - => CSharpCopilotCodeAnalysisService.Create(languageServices, diagnosticsRefresher, copilotOptionService, serviceProvider, brokeredServiceContainer); -} diff --git a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/VisualStudioCopilotOptionService.cs b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/VisualStudioCopilotOptionService.cs deleted file mode 100644 index 89903ca233455..0000000000000 --- a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/VisualStudioCopilotOptionService.cs +++ /dev/null @@ -1,39 +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 System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.Internal.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Settings; - -namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.Analyzer; - -[Export(typeof(VisualStudioCopilotOptionService)), Shared] -internal sealed class VisualStudioCopilotOptionService -{ - private const string CopilotOptionNamePrefix = "Microsoft.VisualStudio.Conversations"; - - private readonly Task _settingsManagerTask; - - [method: ImportingConstructor] - [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioCopilotOptionService( - IVsService settingsManagerService, - IThreadingContext threadingContext) - { - _settingsManagerTask = settingsManagerService.GetValueAsync(threadingContext.DisposalToken); - } - - public async Task IsCopilotOptionEnabledAsync(string optionName) - { - var settingManager = await _settingsManagerTask.ConfigureAwait(false); - // The bool setting is persisted as 0=None, 1=True, 2=False, so it needs to be retrieved as an int. - return settingManager.TryGetValue($"{CopilotOptionNamePrefix}.{optionName}", out int isEnabled) == GetValueResult.Success - && isEnabled == 1; - } -} diff --git a/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs b/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs index aa8265e7d4dbb..7a20c142784b2 100644 --- a/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs +++ b/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper; @@ -16,8 +15,6 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.CodeMapper; -using MapCodeAsyncDelegateType = Func, ImmutableArray<(Document Document, TextSpan TextSpan)>, Dictionary, CancellationToken, Task?>>; - [ExportLanguageService(typeof(IMapCodeService), language: LanguageNames.CSharp), Shared] internal sealed class CSharpMapCodeService : IMapCodeService { @@ -25,9 +22,9 @@ internal sealed class CSharpMapCodeService : IMapCodeService [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpMapCodeService([Import(AllowDefault = true)] ICSharpCopilotMapCodeService? service) + public CSharpMapCodeService(ICSharpCopilotMapCodeService service) { - _service = service ?? new ReflectionWrapper(); + _service = service; } public Task?> MapCodeAsync(Document document, ImmutableArray contents, ImmutableArray<(Document, TextSpan)> focusLocations, CancellationToken cancellationToken) @@ -35,49 +32,4 @@ public CSharpMapCodeService([Import(AllowDefault = true)] ICSharpCopilotMapCodeS var options = new Dictionary(); return _service.MapCodeAsync(document, contents, focusLocations, options, cancellationToken); } - - private sealed class ReflectionWrapper : ICSharpCopilotMapCodeService - { - private const string CodeMapperDllName = "Microsoft.VisualStudio.Copilot.CodeMappers.CSharp, Version=0.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; - private const string MapCodeServiceTypeFullName = "Microsoft.VisualStudio.Conversations.CodeMappers.CSharp.CSharpMapCodeService"; - private const string MapCodeAsyncMethodName = "MapCodeAsync"; - - // Create and cache the delegate to ensure we use a singleton and better performance. - private readonly Lazy _lazyMapCodeAsyncDelegate = new(CreateDelegate, LazyThreadSafetyMode.PublicationOnly); - - private static MapCodeAsyncDelegateType? CreateDelegate() - { - try - { - var assembly = Assembly.Load(CodeMapperDllName); - var type = assembly.GetType(MapCodeServiceTypeFullName); - if (type is null) - return null; - - var serviceInstance = Activator.CreateInstance(type); - if (serviceInstance is null) - return null; - - if (type.GetMethod(MapCodeAsyncMethodName, [typeof(Document), typeof(ImmutableArray), typeof(ImmutableArray<(Document Document, TextSpan TextSpan)>), typeof(Dictionary), typeof(CancellationToken)]) is not MethodInfo mapCodeAsyncMethod) - return null; - - return (MapCodeAsyncDelegateType)Delegate.CreateDelegate(typeof(MapCodeAsyncDelegateType), serviceInstance, mapCodeAsyncMethod); - - } - catch - { - // Catch all here since failure is expected if user has no copilot chat or an older version of it installed. - } - - return null; - } - - public async Task?> MapCodeAsync(Document document, ImmutableArray contents, ImmutableArray<(Document document, TextSpan textSpan)> prioritizedFocusLocations, Dictionary options, CancellationToken cancellationToken) - { - if (_lazyMapCodeAsyncDelegate.Value is null) - return null; - - return await _lazyMapCodeAsyncDelegate.Value(document, contents, prioritizedFocusLocations, options, cancellationToken).ConfigureAwait(false); - } - } } diff --git a/src/Tools/ExternalAccess/Copilot/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/Copilot/InternalAPI.Unshipped.txt index c5a3d62f3596d..0f4024be9597d 100644 --- a/src/Tools/ExternalAccess/Copilot/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/Copilot/InternalAPI.Unshipped.txt @@ -4,12 +4,13 @@ Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper.ICSharpCopilotMapCodeSe Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.Equals(Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper? other) -> bool Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotUtilities -Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCopilotCodeAnalysisService -Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCopilotCodeAnalysisService.AnalyzeDocumentAsync(Microsoft.CodeAnalysis.Document! document, Microsoft.CodeAnalysis.Text.TextSpan? span, string! promptTitle, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! -Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCopilotCodeAnalysisService.GetAvailablePromptTitlesAsync(Microsoft.CodeAnalysis.Document! document, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! -Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCopilotCodeAnalysisService.GetCachedDiagnosticsAsync(Microsoft.CodeAnalysis.Document! document, string! promptTitle, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! -Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCopilotCodeAnalysisService.IsAvailableAsync(System.Threading.CancellationToken cancellation) -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCopilotCodeAnalysisService.StartRefinementSessionAsync(Microsoft.CodeAnalysis.Document! oldDocument, Microsoft.CodeAnalysis.Document! newDocument, Microsoft.CodeAnalysis.Diagnostic? primaryDiagnostic, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.AnalyzeDocumentAsync(Microsoft.CodeAnalysis.Document! document, Microsoft.CodeAnalysis.Text.TextSpan? span, string! promptTitle, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.GetAvailablePromptTitlesAsync(Microsoft.CodeAnalysis.Document! document, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.GetCachedDiagnosticsAsync(Microsoft.CodeAnalysis.Document! document, string! promptTitle, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.GetOnTheFlyDocsAsync(string! symbolSignature, System.Collections.Immutable.ImmutableArray declarationCode, string! language, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.IsAvailableAsync(System.Threading.CancellationToken cancellation) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.StartRefinementSessionAsync(Microsoft.CodeAnalysis.Document! oldDocument, Microsoft.CodeAnalysis.Document! newDocument, Microsoft.CodeAnalysis.Diagnostic? primaryDiagnostic, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! override Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.Equals(object? obj) -> bool override Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.GetHashCode() -> int static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.Create(System.Collections.Immutable.ImmutableArray values) -> Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper! diff --git a/src/Tools/ExternalAccess/Debugger/GlassTestsHotReloadService.cs b/src/Tools/ExternalAccess/Debugger/GlassTestsHotReloadService.cs index 548f6f9f3e7f0..c53ee85331017 100644 --- a/src/Tools/ExternalAccess/Debugger/GlassTestsHotReloadService.cs +++ b/src/Tools/ExternalAccess/Debugger/GlassTestsHotReloadService.cs @@ -53,22 +53,22 @@ private DebuggingSessionId GetSessionId() public void EnterBreakState() { - _encService.BreakStateOrCapabilitiesChanged(GetSessionId(), inBreakState: true, out _); + _encService.BreakStateOrCapabilitiesChanged(GetSessionId(), inBreakState: true); } public void ExitBreakState() { - _encService.BreakStateOrCapabilitiesChanged(GetSessionId(), inBreakState: false, out _); + _encService.BreakStateOrCapabilitiesChanged(GetSessionId(), inBreakState: false); } public void OnCapabilitiesChanged() { - _encService.BreakStateOrCapabilitiesChanged(GetSessionId(), inBreakState: null, out _); + _encService.BreakStateOrCapabilitiesChanged(GetSessionId(), inBreakState: null); } public void CommitSolutionUpdate() { - _encService.CommitSolutionUpdate(GetSessionId(), out _); + _encService.CommitSolutionUpdate(GetSessionId()); } public void DiscardSolutionUpdate() @@ -78,7 +78,7 @@ public void DiscardSolutionUpdate() public void EndDebuggingSession() { - _encService.EndDebuggingSession(GetSessionId(), out _); + _encService.EndDebuggingSession(GetSessionId()); _sessionId = default; } diff --git a/src/Tools/ExternalAccess/FSharp/FSharpGlobalOptions.cs b/src/Tools/ExternalAccess/FSharp/FSharpGlobalOptions.cs index ebb4987c5faca..1735f0020ece9 100644 --- a/src/Tools/ExternalAccess/FSharp/FSharpGlobalOptions.cs +++ b/src/Tools/ExternalAccess/FSharp/FSharpGlobalOptions.cs @@ -36,6 +36,9 @@ public void SetBackgroundAnalysisScope(bool openFilesOnly) _globalOptions.SetGlobalOption( SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.FSharp, openFilesOnly ? BackgroundAnalysisScope.OpenFiles : BackgroundAnalysisScope.FullSolution); + _globalOptions.SetGlobalOption( + SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.FSharp, + openFilesOnly ? CompilerDiagnosticsScope.OpenFiles : CompilerDiagnosticsScope.FullSolution); } } } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs index f77fd03c4b738..b4fef38d66eef 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs @@ -26,8 +26,6 @@ public FSharpDiagnosticAnalyzerService(Microsoft.CodeAnalysis.Diagnostics.IDiagn } public void Reanalyze(Workspace workspace, IEnumerable projectIds = null, IEnumerable documentIds = null, bool highPriority = false) - { - _delegatee.Reanalyze(workspace, projectIds, documentIds, highPriority); - } + => _delegatee.RequestDiagnosticRefresh(); } } diff --git a/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs b/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs index 42525a23e8f98..fe18ef4273008 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -37,12 +36,12 @@ public async Task SearchDocumentAsync( Document document, string searchPattern, IImmutableSet kinds, - Func onResultFound, + Func, Task> onResultsFound, CancellationToken cancellationToken) { var results = await _service.SearchDocumentAsync(document, searchPattern, kinds, cancellationToken).ConfigureAwait(false); - foreach (var result in results) - await onResultFound(new InternalFSharpNavigateToSearchResult(result)).ConfigureAwait(false); + if (results.Length > 0) + await onResultsFound(results.SelectAsArray(result => (INavigateToSearchResult)new InternalFSharpNavigateToSearchResult(result))).ConfigureAwait(false); } public async Task SearchProjectsAsync( @@ -52,7 +51,7 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -62,8 +61,8 @@ public async Task SearchProjectsAsync( foreach (var project in projects) { var results = await _service.SearchProjectAsync(project, priorityDocuments, searchPattern, kinds, cancellationToken).ConfigureAwait(false); - foreach (var result in results) - await onResultFound(project, new InternalFSharpNavigateToSearchResult(result)).ConfigureAwait(false); + if (results.Length > 0) + await onResultsFound(results.SelectAsArray(result => (INavigateToSearchResult)new InternalFSharpNavigateToSearchResult(result))).ConfigureAwait(false); await onProjectCompleted().ConfigureAwait(false); } diff --git a/src/Tools/ExternalAccess/FSharp/SignatureHelp/FSharpSignatureHelpItem.cs b/src/Tools/ExternalAccess/FSharp/SignatureHelp/FSharpSignatureHelpItem.cs index 533f7bf22ce91..466d492e3ab5b 100644 --- a/src/Tools/ExternalAccess/FSharp/SignatureHelp/FSharpSignatureHelpItem.cs +++ b/src/Tools/ExternalAccess/FSharp/SignatureHelp/FSharpSignatureHelpItem.cs @@ -36,8 +36,7 @@ internal class FSharpSignatureHelpItem public Func> DocumentationFactory { get; } - private static readonly Func> s_emptyDocumentationFactory = - _ => SpecializedCollections.EmptyEnumerable(); + private static readonly Func> s_emptyDocumentationFactory = _ => []; public FSharpSignatureHelpItem( bool isVariadic, diff --git a/src/Tools/ExternalAccess/FSharp/SignatureHelp/FSharpSignatureHelpParameter.cs b/src/Tools/ExternalAccess/FSharp/SignatureHelp/FSharpSignatureHelpParameter.cs index 45f88411dce65..27b11871486b3 100644 --- a/src/Tools/ExternalAccess/FSharp/SignatureHelp/FSharpSignatureHelpParameter.cs +++ b/src/Tools/ExternalAccess/FSharp/SignatureHelp/FSharpSignatureHelpParameter.cs @@ -53,8 +53,7 @@ internal class FSharpSignatureHelpParameter /// public IList SelectedDisplayParts { get; } - private static readonly Func> s_emptyDocumentationFactory = - _ => SpecializedCollections.EmptyEnumerable(); + private static readonly Func> s_emptyDocumentationFactory = _ => []; public FSharpSignatureHelpParameter( string name, diff --git a/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs b/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs index bc68564d8777f..49275c191a188 100644 --- a/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Navigation; using Microsoft.CodeAnalysis.NavigateTo; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.NavigateTo; @@ -25,38 +26,35 @@ public static Task SearchAsync( var searcher = NavigateToSearcher.Create( solution, AsynchronousOperationListenerProvider.NullListener, - new OmniSharpNavigateToCallbackImpl(callback), + new OmniSharpNavigateToCallbackImpl(solution, callback), searchPattern, kinds, disposalToken: CancellationToken.None); - return searcher.SearchAsync(searchCurrentDocument: false, cancellationToken); + return searcher.SearchAsync(NavigateToSearchScope.Solution, cancellationToken); } - private sealed class OmniSharpNavigateToCallbackImpl : INavigateToSearchCallback + private sealed class OmniSharpNavigateToCallbackImpl(Solution solution, OmniSharpNavigateToCallback callback) : INavigateToSearchCallback { - private readonly OmniSharpNavigateToCallback _callback; - - public OmniSharpNavigateToCallbackImpl(OmniSharpNavigateToCallback callback) - { - _callback = callback; - } - - public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(project.Solution, cancellationToken).ConfigureAwait(false); - var omniSharpResult = new OmniSharpNavigateToSearchResult( - result.AdditionalInformation, - result.Kind, - (OmniSharpNavigateToMatchKind)result.MatchKind, - result.IsCaseSensitive, - result.Name, - result.NameMatchSpans, - result.SecondarySort, - result.Summary!, - new OmniSharpNavigableItem(result.NavigableItem.DisplayTaggedParts, document, result.NavigableItem.SourceSpan)); + foreach (var result in results) + { + var project = solution.GetRequiredProject(result.NavigableItem.Document.Project.Id); + var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(project.Solution, cancellationToken).ConfigureAwait(false); + var omniSharpResult = new OmniSharpNavigateToSearchResult( + result.AdditionalInformation, + result.Kind, + (OmniSharpNavigateToMatchKind)result.MatchKind, + result.IsCaseSensitive, + result.Name, + result.NameMatchSpans, + result.SecondarySort, + result.Summary!, + new OmniSharpNavigableItem(result.NavigableItem.DisplayTaggedParts, document, result.NavigableItem.SourceSpan)); - await _callback(project, omniSharpResult, cancellationToken).ConfigureAwait(false); + await callback(project, omniSharpResult, cancellationToken).ConfigureAwait(false); + } } public void Done(bool isFullyLoaded) diff --git a/src/Tools/ExternalAccess/Razor/IRazorLanguageServerFactory.cs b/src/Tools/ExternalAccess/Razor/AbstractRazorLanguageServerFactoryWrapper.cs similarity index 70% rename from src/Tools/ExternalAccess/Razor/IRazorLanguageServerFactory.cs rename to src/Tools/ExternalAccess/Razor/AbstractRazorLanguageServerFactoryWrapper.cs index 0569cc2b90546..93dc75dd8acda 100644 --- a/src/Tools/ExternalAccess/Razor/IRazorLanguageServerFactory.cs +++ b/src/Tools/ExternalAccess/Razor/AbstractRazorLanguageServerFactoryWrapper.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 Microsoft.CodeAnalysis.Host; using Newtonsoft.Json; @@ -12,11 +13,11 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor /// /// NOTE: For Razor test usage only /// - internal interface IRazorLanguageServerFactoryWrapper + internal abstract class AbstractRazorLanguageServerFactoryWrapper { - IRazorLanguageServerTarget CreateLanguageServer(JsonRpc jsonRpc, IRazorTestCapabilitiesProvider capabilitiesProvider, HostServices hostServices); + internal abstract IRazorLanguageServerTarget CreateLanguageServer(JsonRpc jsonRpc, JsonSerializer jsonSerializer, IRazorTestCapabilitiesProvider capabilitiesProvider, HostServices hostServices); - DocumentInfo CreateDocumentInfo( + internal abstract DocumentInfo CreateDocumentInfo( DocumentId id, string name, IReadOnlyList? folders = null, @@ -30,6 +31,6 @@ DocumentInfo CreateDocumentInfo( /// /// Supports the creation of a Roslyn LSP server for functional tests /// - void AddJsonConverters(JsonSerializer jsonSerializer); + internal abstract void AddJsonConverters(JsonSerializer jsonSerializer); } } diff --git a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs index 520faed5e9018..71a70b2de65d9 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis.LanguageServer; @@ -9,9 +10,14 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; internal static class Constants { + public const string RazorLanguageName = LanguageInfoProvider.RazorLanguageName; + public const string RazorLSPContentType = "Razor"; public const string RazorLanguageContract = ProtocolConstants.RazorCohostContract; public static readonly ImmutableArray RazorLanguage = ImmutableArray.Create("Razor"); + + // The UI context is provided by Razor, so this guid must match the one in https://github.com/dotnet/razor/blob/main/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs + public static readonly Guid RazorCohostingUIContext = new Guid("6d5b86dc-6b8a-483b-ae30-098a3c7d6774"); } diff --git a/src/Tools/ExternalAccess/Razor/Cohost/ExportCohostLspServiceFactoryAttribute.cs b/src/Tools/ExternalAccess/Razor/Cohost/ExportCohostLspServiceFactoryAttribute.cs new file mode 100644 index 0000000000000..a37a4c9862cda --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/ExportCohostLspServiceFactoryAttribute.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; +using System.Composition; +using Microsoft.CodeAnalysis.LanguageServer; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +[AttributeUsage(AttributeTargets.Class), MetadataAttribute] +internal class ExportCohostLspServiceFactoryAttribute(Type handlerType) : ExportLspServiceFactoryAttribute(handlerType, ProtocolConstants.RoslynLspLanguagesContract, WellKnownLspServerKinds.AlwaysActiveVSLspServer); diff --git a/src/Tools/ExternalAccess/Razor/Cohost/ExportCohostStatelessLspServiceAttribute.cs b/src/Tools/ExternalAccess/Razor/Cohost/ExportCohostStatelessLspServiceAttribute.cs new file mode 100644 index 0000000000000..f46dfb287c93c --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/ExportCohostStatelessLspServiceAttribute.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; +using System.Composition; +using Microsoft.CodeAnalysis.LanguageServer; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +[AttributeUsage(AttributeTargets.Class), MetadataAttribute] +internal sealed class ExportCohostStatelessLspServiceAttribute(Type handlerType) : ExportStatelessLspServiceAttribute(handlerType, ProtocolConstants.RoslynLspLanguagesContract, WellKnownLspServerKinds.AlwaysActiveVSLspServer); diff --git a/src/Tools/ExternalAccess/Razor/Cohost/IRazorCohostDynamicRegistrationService.cs b/src/Tools/ExternalAccess/Razor/Cohost/IRazorCohostDynamicRegistrationService.cs new file mode 100644 index 0000000000000..94156a79011a4 --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/IRazorCohostDynamicRegistrationService.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 System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +internal interface IRazorCohostDynamicRegistrationService +{ + Task RegisterAsync(string serializedClientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.cs b/src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.cs new file mode 100644 index 0000000000000..5984d729b25e6 --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +internal interface IRazorSemanticTokensRefreshQueue : ILspService +{ + /// + /// Initialize the semantic tokens refresh queue in Roslyn + /// + /// + /// This MUST be called synchronously from an IOnInitialized handler, to avoid dual initialization when + /// Roslyn and Razor both support semantic tokens + /// + void Initialize(string clientCapabilitiesString); + + Task TryEnqueueRefreshComputationAsync(Project project, CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManager.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManager.cs new file mode 100644 index 0000000000000..bde9f3741451f --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManager.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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +internal class RazorCohostClientLanguageServerManager(IClientLanguageServerManager clientLanguageServerManager) : IRazorCohostClientLanguageServerManager +{ + public Task SendRequestAsync(string methodName, TParams @params, CancellationToken cancellationToken) + => clientLanguageServerManager.SendRequestAsync(methodName, @params, cancellationToken); + + public ValueTask SendRequestAsync(string methodName, CancellationToken cancellationToken) + => clientLanguageServerManager.SendRequestAsync(methodName, cancellationToken); + + public ValueTask SendRequestAsync(string methodName, TParams @params, CancellationToken cancellationToken) + => clientLanguageServerManager.SendRequestAsync(methodName, @params, cancellationToken); + + public ValueTask SendNotificationAsync(string methodName, CancellationToken cancellationToken) + => clientLanguageServerManager.SendNotificationAsync(methodName, cancellationToken); + + public ValueTask SendNotificationAsync(string methodName, TParams @params, CancellationToken cancellationToken) + => clientLanguageServerManager.SendNotificationAsync(methodName, @params, cancellationToken); +} diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManagerFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManagerFactory.cs index 4f5b630e193c7..2a06fa9b68cbd 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManagerFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManagerFactory.cs @@ -4,14 +4,13 @@ using System; using System.Composition; -using System.Threading.Tasks; -using System.Threading; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; -[ExportRazorLspServiceFactory(typeof(IRazorCohostClientLanguageServerManager)), Shared] +[Shared] +[ExportCohostLspServiceFactory(typeof(IRazorCohostClientLanguageServerManager))] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class RazorCohostClientLanguageServerManagerFactory() : ILspServiceFactory @@ -22,22 +21,4 @@ public ILspService CreateILspService(LspServices lspServices, WellKnownLspServer return new RazorCohostClientLanguageServerManager(notificationManager); } - - internal class RazorCohostClientLanguageServerManager(IClientLanguageServerManager clientLanguageServerManager) : IRazorCohostClientLanguageServerManager - { - public Task SendRequestAsync(string methodName, TParams @params, CancellationToken cancellationToken) - => clientLanguageServerManager.SendRequestAsync(methodName, @params, cancellationToken); - - public ValueTask SendRequestAsync(string methodName, CancellationToken cancellationToken) - => clientLanguageServerManager.SendRequestAsync(methodName, cancellationToken); - - public ValueTask SendRequestAsync(string methodName, TParams @params, CancellationToken cancellationToken) - => clientLanguageServerManager.SendRequestAsync(methodName, @params, cancellationToken); - - public ValueTask SendNotificationAsync(string methodName, CancellationToken cancellationToken) - => clientLanguageServerManager.SendNotificationAsync(methodName, cancellationToken); - - public ValueTask SendNotificationAsync(string methodName, TParams @params, CancellationToken cancellationToken) - => clientLanguageServerManager.SendNotificationAsync(methodName, @params, cancellationToken); - } } diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostServerClientLanguageServerManagerFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostServerClientLanguageServerManagerFactory.cs new file mode 100644 index 0000000000000..5fa2a36d1daf6 --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostServerClientLanguageServerManagerFactory.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.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +// This will be removed in future when the cohost server is removed, and we move to dynamic registration. +// It's already marked as Obsolete though, because Roslyn MEF rules :) + +[Shared] +[ExportRazorLspServiceFactory(typeof(IRazorCohostClientLanguageServerManager))] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class RazorCohostServerClientLanguageServerManagerFactory() : ILspServiceFactory +{ + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + { + var notificationManager = lspServices.GetRequiredService(); + + return new RazorCohostClientLanguageServerManager(notificationManager); + } +} diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs new file mode 100644 index 0000000000000..425cfce0e740c --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.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.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Newtonsoft.Json; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +[ExportCSharpVisualBasicLspServiceFactory(typeof(RazorDynamicRegistrationService), WellKnownLspServerKinds.AlwaysActiveVSLspServer), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class RazorDynamicRegistrationServiceFactory( + [Import(AllowDefault = true)] IUIContextActivationService? uIContextActivationService, + [Import(AllowDefault = true)] Lazy? dynamicRegistrationService) : ILspServiceFactory +{ + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + { + var clientLanguageServerManager = lspServices.GetRequiredService(); + + return new RazorDynamicRegistrationService(uIContextActivationService, dynamicRegistrationService, clientLanguageServerManager); + } + + private class RazorDynamicRegistrationService( + IUIContextActivationService? uIContextActivationService, + Lazy? dynamicRegistrationService, + IClientLanguageServerManager? clientLanguageServerManager) : ILspService, IOnInitialized, IDisposable + { + private readonly CancellationTokenSource _disposalTokenSource = new(); + + public void Dispose() + { + _disposalTokenSource.Cancel(); + } + + public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) + { + if (dynamicRegistrationService is null || clientLanguageServerManager is null) + { + return Task.CompletedTask; + } + + if (uIContextActivationService is null) + { + // Outside of VS, we want to initialize immediately.. I think? + InitializeRazor(); + } + else + { + uIContextActivationService.ExecuteWhenActivated(Constants.RazorCohostingUIContext, InitializeRazor); + } + + return Task.CompletedTask; + + void InitializeRazor() + { + this.InitializeRazor(clientCapabilities, context, _disposalTokenSource.Token); + } + } + + private void InitializeRazor(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) + { + // The LSP server will dispose us when the server exits, but VS could decide to activate us later. + // If a new instance of the server is created, a new instance of this class will be created and the + // UIContext will already be active, so this method will be immediately called on the new instance. + if (cancellationToken.IsCancellationRequested) return; + + var serializer = new JsonSerializer(); + serializer.AddVSInternalExtensionConverters(); + var serializerSettings = new JsonSerializerSettings { Converters = serializer.Converters }; + + // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL + var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities, serializerSettings); + var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(clientLanguageServerManager!); + + var requestContext = new RazorCohostRequestContext(context); + dynamicRegistrationService!.Value.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken).ReportNonFatalErrorAsync(); + } + } +} diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorMethodAttribute.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorMethodAttribute.cs index 44d4bfd2d6fdd..d0917c5ff51d8 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorMethodAttribute.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorMethodAttribute.cs @@ -8,9 +8,13 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; [MetadataAttribute] -internal sealed class RazorMethodAttribute : LanguageServerEndpointAttribute +internal class RazorMethodAttribute : LanguageServerEndpointAttribute { public RazorMethodAttribute(string method) : base(method, LanguageServerConstants.DefaultLanguageName) { } + + public RazorMethodAttribute(string method, string language) : base(method, language) + { + } } diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs new file mode 100644 index 0000000000000..52c7cafc8021c --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.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.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; +using Newtonsoft.Json; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +[ExportCohostLspServiceFactory(typeof(IRazorSemanticTokensRefreshQueue)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class RazorSemanticTokensRefreshQueueWrapperFactory() : ILspServiceFactory +{ + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + { + var semanticTokensRefreshQueue = lspServices.GetRequiredService(); + + return new RazorSemanticTokensRefreshQueueWrapper(semanticTokensRefreshQueue); + } + + internal class RazorSemanticTokensRefreshQueueWrapper(SemanticTokensRefreshQueue semanticTokensRefreshQueue) : IRazorSemanticTokensRefreshQueue, IDisposable + { + public void Initialize(string clientCapabilitiesString) + { + var clientCapabilities = JsonConvert.DeserializeObject(clientCapabilitiesString) ?? new(); + // If Roslyn and Razor both support semantic tokens, then this call to Initialize is redundant, but the + // Initialize method in the queue itself is resilient to being called twice, so it doesn't actually do + // any harm. + semanticTokensRefreshQueue.Initialize(clientCapabilities); + } + + public Task TryEnqueueRefreshComputationAsync(Project project, CancellationToken cancellationToken) + => semanticTokensRefreshQueue.TryEnqueueRefreshComputationAsync(project, cancellationToken); + + public void Dispose() + { + semanticTokensRefreshQueue.Dispose(); + } + } +} diff --git a/src/Tools/ExternalAccess/Razor/RazorLanguageServerFactoryWrapper.cs b/src/Tools/ExternalAccess/Razor/RazorLanguageServerFactoryWrapper.cs index e99bb674778a6..989fc0e911cfb 100644 --- a/src/Tools/ExternalAccess/Razor/RazorLanguageServerFactoryWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/RazorLanguageServerFactoryWrapper.cs @@ -15,9 +15,9 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor { - [Export(typeof(IRazorLanguageServerFactoryWrapper))] + [Export(typeof(AbstractRazorLanguageServerFactoryWrapper))] [Shared] - internal class RazorLanguageServerFactoryWrapper : IRazorLanguageServerFactoryWrapper + internal class RazorLanguageServerFactoryWrapper : AbstractRazorLanguageServerFactoryWrapper { private readonly ILanguageServerFactory _languageServerFactory; @@ -33,15 +33,15 @@ public RazorLanguageServerFactoryWrapper(ILanguageServerFactory languageServerFa _languageServerFactory = languageServerFactory; } - public IRazorLanguageServerTarget CreateLanguageServer(JsonRpc jsonRpc, IRazorTestCapabilitiesProvider razorCapabilitiesProvider, HostServices hostServices) + internal override IRazorLanguageServerTarget CreateLanguageServer(JsonRpc jsonRpc, JsonSerializer jsonSerializer, IRazorTestCapabilitiesProvider razorCapabilitiesProvider, HostServices hostServices) { var capabilitiesProvider = new RazorCapabilitiesProvider(razorCapabilitiesProvider); - var languageServer = _languageServerFactory.Create(jsonRpc, capabilitiesProvider, WellKnownLspServerKinds.RazorLspServer, NoOpLspLogger.Instance, hostServices); + var languageServer = _languageServerFactory.Create(jsonRpc, jsonSerializer, capabilitiesProvider, WellKnownLspServerKinds.RazorLspServer, NoOpLspLogger.Instance, hostServices); return new RazorLanguageServerTargetWrapper(languageServer); } - public DocumentInfo CreateDocumentInfo( + internal override DocumentInfo CreateDocumentInfo( DocumentId id, string name, IReadOnlyList? folders = null, @@ -65,7 +65,7 @@ public DocumentInfo CreateDocumentInfo( .WithDocumentServiceProvider(documentServiceProvider); } - public void AddJsonConverters(JsonSerializer jsonSerializer) + internal override void AddJsonConverters(JsonSerializer jsonSerializer) { VSInternalExtensionUtilities.AddVSInternalExtensionConverters(jsonSerializer); } diff --git a/src/Tools/ExternalAccess/Razor/RazorTestAnalyzerLoader.cs b/src/Tools/ExternalAccess/Razor/RazorTestAnalyzerLoader.cs index b27bd3359fed1..03b807c66d944 100644 --- a/src/Tools/ExternalAccess/Razor/RazorTestAnalyzerLoader.cs +++ b/src/Tools/ExternalAccess/Razor/RazorTestAnalyzerLoader.cs @@ -4,32 +4,25 @@ using System; using System.Composition; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.SolutionCrawler; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor; [Export(typeof(RazorTestAnalyzerLoader)), Shared] internal class RazorTestAnalyzerLoader { - private readonly IDiagnosticAnalyzerService _analyzerService; - private readonly DiagnosticService _diagnosticService; - [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public RazorTestAnalyzerLoader(IDiagnosticAnalyzerService analyzerService, IDiagnosticService diagnosticService) + public RazorTestAnalyzerLoader() { - _analyzerService = analyzerService; - _diagnosticService = (DiagnosticService)diagnosticService; } #pragma warning disable IDE0060 // Remove unused parameter +#pragma warning disable CA1822 // Mark members as static public void InitializeDiagnosticsServices(Workspace workspace) +#pragma warning restore CA1822 // Mark members as static #pragma warning restore IDE0060 // Remove unused parameter { - _diagnosticService.Register((IDiagnosticUpdateSource)_analyzerService); } public static IAnalyzerAssemblyLoader CreateAnalyzerAssemblyLoader() diff --git a/src/Tools/ExternalAccess/RazorTest/Cohost/RazorCohostTests.cs b/src/Tools/ExternalAccess/RazorTest/Cohost/RazorCohostTests.cs index 3ffcfff8da827..0ee0abd4523d6 100644 --- a/src/Tools/ExternalAccess/RazorTest/Cohost/RazorCohostTests.cs +++ b/src/Tools/ExternalAccess/RazorTest/Cohost/RazorCohostTests.cs @@ -59,7 +59,7 @@ public async Task TestExternalAccessRazorHandlerInvoked() } }; - var response = await server.GetTestAccessor().ExecuteRequestAsync(RazorHandler.MethodName, request, CancellationToken.None); + var response = await server.GetTestAccessor().ExecuteRequestAsync(RazorHandler.MethodName, LanguageServerConstants.DefaultLanguageName, request, CancellationToken.None); Assert.NotNull(response); Assert.Equal(document.GetURI(), response.DocumentUri); @@ -88,7 +88,7 @@ public async Task TestProjectContextHandler() } }; - var response = await server.GetTestAccessor().ExecuteRequestAsync(VSMethods.GetProjectContextsName, request, CancellationToken.None); + var response = await server.GetTestAccessor().ExecuteRequestAsync(VSMethods.GetProjectContextsName, LanguageServerConstants.DefaultLanguageName, request, CancellationToken.None); Assert.NotNull(response); var projectContext = Assert.Single(response?.ProjectContexts); @@ -118,7 +118,7 @@ public async Task TestDocumentSync() } }; - await server.GetTestAccessor().ExecuteRequestAsync(Methods.TextDocumentDidOpenName, didOpenRequest, CancellationToken.None); + await server.GetTestAccessor().ExecuteRequestAsync(Methods.TextDocumentDidOpenName, LanguageServerConstants.DefaultLanguageName, didOpenRequest, CancellationToken.None); var workspaceManager = server.GetLspServices().GetRequiredService(); Assert.True(workspaceManager.GetTrackedLspText().TryGetValue(document.GetURI(), out var trackedText)); @@ -144,7 +144,7 @@ public async Task TestDocumentSync() ] }; - await server.GetTestAccessor().ExecuteRequestAsync(Methods.TextDocumentDidChangeName, didChangeRequest, CancellationToken.None); + await server.GetTestAccessor().ExecuteRequestAsync(Methods.TextDocumentDidChangeName, LanguageServerConstants.DefaultLanguageName, didChangeRequest, CancellationToken.None); Assert.True(workspaceManager.GetTrackedLspText().TryGetValue(document.GetURI(), out trackedText)); Assert.Equal("Not The Original text", trackedText.Text.ToString()); @@ -167,7 +167,7 @@ private static async Task> InitializeLang var serverAccessor = server!.GetTestAccessor(); - await serverAccessor.ExecuteRequestAsync(Methods.InitializeName, new InitializeParams { Capabilities = new() }, CancellationToken.None); + await serverAccessor.ExecuteRequestAsync(Methods.InitializeName, LanguageServerConstants.DefaultLanguageName, new InitializeParams { Capabilities = new() }, CancellationToken.None); return server; } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs new file mode 100644 index 0000000000000..c7b511ee906c2 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using LSP = Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +internal sealed class HotReloadRequestContext(RequestContext context) +{ + internal LSP.ClientCapabilities ClientCapabilities => context.GetRequiredClientCapabilities(); + public TextDocument? TextDocument => context.TextDocument; + public Solution? Solution => context.Solution; + public bool IsTracking(TextDocument textDocument) => context.IsTracking(textDocument.GetURI()); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs new file mode 100644 index 0000000000000..f1fe7a600dae4 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.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.Collections.Generic; +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +internal interface IHotReloadDiagnosticManager +{ + /// + /// Refreshes hot reload diagnostics. + /// + void RequestRefresh(); + + /// + /// Registers providers of hot reload diagnostics. Callers are responsible for refreshing diagnostics after registration. + /// + void Register(IEnumerable providers); + + /// + /// Unregisters providers of hot reload diagnostics. Callers are responsible for refreshing diagnostics after un-registration. + /// + void Unregister(IEnumerable providers); + + /// + /// Providers. + /// + ImmutableArray Providers { get; } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs new file mode 100644 index 0000000000000..85a8004f1db8d --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.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 System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +/// +/// Source of hot reload diagnostics. +/// +internal interface IHotReloadDiagnosticSource +{ + /// + /// Text document for which diagnostics are provided. + /// + DocumentId DocumentId { get; } + + /// + /// Provides list of diagnostics for the given document. + /// + ValueTask> GetDiagnosticsAsync(HotReloadRequestContext request, CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..586a6a2d8ec90 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +/// +/// Provides diagnostic sources. +/// +internal interface IHotReloadDiagnosticSourceProvider +{ + /// + /// True if this provider is for documents. False if it is for a workspace, i.e. for unopened documents. + /// + bool IsDocument { get; } + + /// + /// Creates the diagnostic sources. + /// + /// The context. + /// The cancellation token. + ValueTask> CreateDiagnosticSourcesAsync(HotReloadRequestContext context, CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IVisualDiagnosticsLanguageService.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IVisualDiagnosticsLanguageService.cs new file mode 100644 index 0000000000000..fa0bfc3b929b4 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IVisualDiagnosticsLanguageService.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.ServiceHub.Framework; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +{ + /// + /// Workspace service responsible for starting a Visual Diagnostic session on the LSP server + /// + internal interface IVisualDiagnosticsLanguageService : IWorkspaceService, IDisposable + { + /// + /// Initialize the diagnostic host + /// + /// Service broker + /// Cancellation token + /// + Task InitializeAsync(IServiceBroker serviceBroker, CancellationToken token); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs new file mode 100644 index 0000000000000..8a6071ea4228a --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.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.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +[Export(typeof(IHotReloadDiagnosticManager)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class HotReloadDiagnosticManager([Import] IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager +{ + private readonly object _syncLock = new(); + public ImmutableArray Providers { get; private set; } = []; + + public void RequestRefresh() + => diagnosticsRefresher.RequestWorkspaceRefresh(); + + public void Register(IEnumerable providers) + { + // We use array instead of e.g. HashSet because we expect the number of sources to be small. + // Usually 2, one workspace and one document provider. + lock (_syncLock) + { + foreach (var provider in providers) + { + if (!Providers.Contains(provider)) + Providers = Providers.Add(provider); + } + } + } + + public void Unregister(IEnumerable providers) + { + lock (_syncLock) + { + foreach (var provider in Providers) + Providers = Providers.Remove(provider); + } + } + +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs new file mode 100644 index 0000000000000..64d157b4a1343 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +internal sealed class HotReloadDiagnosticSource(IHotReloadDiagnosticSource source, TextDocument textDocument) : IDiagnosticSource +{ + public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + var diagnostics = await source.GetDiagnosticsAsync(new HotReloadRequestContext(context), cancellationToken).ConfigureAwait(false); + var result = diagnostics.Select(diagnostic => DiagnosticData.Create(diagnostic, textDocument)).ToImmutableArray(); + return result; + } + + public TextDocumentIdentifier? GetDocumentIdentifier() => new() { Uri = textDocument.GetURI() }; + public ProjectOrDocumentId GetId() => new(textDocument.Id); + public Project GetProject() => textDocument.Project; + public bool IsLiveSource() => true; + public string ToDisplayString() => $"{this.GetType().Name}: {textDocument.FilePath ?? textDocument.Name} in {textDocument.Project.Name}"; +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..88bd5cceb9f90 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.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.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +internal abstract class HotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager diagnosticManager, bool isDocument) : IDiagnosticSourceProvider +{ + public string Name => "HotReloadDiagnostics"; + public bool IsDocument => isDocument; + + public bool IsEnabled(ClientCapabilities clientCapabilities) => true; + + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.Solution is not Solution solution) + { + return ImmutableArray.Empty; + } + + var hotReloadContext = new HotReloadRequestContext(context); + using var _ = ArrayBuilder.GetInstance(out var sources); + foreach (var provider in diagnosticManager.Providers) + { + if (provider.IsDocument == isDocument) + { + var hotReloadSources = await provider.CreateDiagnosticSourcesAsync(hotReloadContext, cancellationToken).ConfigureAwait(false); + foreach (var hotReloadSource in hotReloadSources) + { + // Look for additional document first. Currently most common hot reload diagnostics come from *.xaml files. + TextDocument? textDocument = + solution.GetAdditionalDocument(hotReloadSource.DocumentId) ?? + solution.GetDocument(hotReloadSource.DocumentId); + if (textDocument != null) + { + sources.Add(new HotReloadDiagnosticSource(hotReloadSource, textDocument)); + } + } + } + } + + var result = sources.ToImmutableAndClear(); + return DiagnosticSourceManager.AggregateSourcesIfNeeded(result, isDocument); + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class DocumentHotReloadDiagnosticSourceProvider([Import] IHotReloadDiagnosticManager diagnosticManager) + : HotReloadDiagnosticSourceProvider(diagnosticManager, isDocument: true) + { + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class WorkspaceHotReloadDiagnosticSourceProvider([Import] IHotReloadDiagnosticManager diagnosticManager) + : HotReloadDiagnosticSourceProvider(diagnosticManager, isDocument: false) + { + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/VisualDiagnosticsServiceFactory.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/VisualDiagnosticsServiceFactory.cs new file mode 100644 index 0000000000000..ce9a367059a7d --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/VisualDiagnosticsServiceFactory.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.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.BrokeredServices; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.ServiceHub.Framework; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics; + +/// +/// LSP Service responsible for loading IVisualDiagnosticsLanguageService workspace service and delegate the broker service to the workspace service, +/// and handling MAUI XAML/C#/CSS/Razor Hot Reload support +/// +[Export(typeof(IOnServiceBrokerInitialized))] +[ExportCSharpVisualBasicLspServiceFactory(typeof(OnInitializedService)), Shared] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +[method: ImportingConstructor] +internal sealed class VisualDiagnosticsServiceFactory( + LspWorkspaceRegistrationService lspWorkspaceRegistrationService) : ILspServiceFactory, IOnServiceBrokerInitialized +{ + private readonly LspWorkspaceRegistrationService _lspWorkspaceRegistrationService = lspWorkspaceRegistrationService; + private readonly Lazy _OnInitializedService = new Lazy(() => new OnInitializedService(lspWorkspaceRegistrationService)); + + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + { + return _OnInitializedService.Value; + } + + public void OnServiceBrokerInitialized(IServiceBroker serviceBroker) + { + _OnInitializedService.Value.OnServiceBrokerInitialized(serviceBroker); + } + + private class OnInitializedService : ILspService, IOnInitialized, IOnServiceBrokerInitialized, IDisposable + { + private readonly LspWorkspaceRegistrationService _lspWorkspaceRegistrationService; + private IVisualDiagnosticsLanguageService? _visualDiagnosticsLanguageService; + private CancellationToken _cancellationToken; + private static readonly TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); + + public OnInitializedService(LspWorkspaceRegistrationService lspWorkspaceRegistrationService) + { + _lspWorkspaceRegistrationService = lspWorkspaceRegistrationService; + } + + public void Dispose() + { + (_visualDiagnosticsLanguageService as IDisposable)?.Dispose(); + } + + public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) + { + _cancellationToken = cancellationToken; + _taskCompletionSource.TrySetResult(true); + return Task.CompletedTask; + } + + public void OnServiceBrokerInitialized(IServiceBroker serviceBroker) + { + _taskCompletionSource.Task.ContinueWith((initialized) => OnInitializeVisualDiagnosticsLanguageServiceAsync(serviceBroker), TaskScheduler.Default); + } + + private async Task OnInitializeVisualDiagnosticsLanguageServiceAsync(IServiceBroker serviceBroker) + { + // initialize VisualDiagnosticsLanguageService + Workspace workspace = _lspWorkspaceRegistrationService.GetAllRegistrations().First(w => w.Kind == WorkspaceKind.Host); + Contract.ThrowIfFalse(workspace != null, "We should always have a host workspace."); + + IVisualDiagnosticsLanguageService? visualDiagnosticsLanguageService = workspace.Services.GetService(); + + if (visualDiagnosticsLanguageService != null) + { + await visualDiagnosticsLanguageService.InitializeAsync(serviceBroker, _cancellationToken).ConfigureAwait(false); + _visualDiagnosticsLanguageService = visualDiagnosticsLanguageService; + } + } + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Shipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Shipped.txt new file mode 100644 index 0000000000000..7dc5c58110bfa --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt new file mode 100644 index 0000000000000..a831db9ed8865 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -0,0 +1,50 @@ +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.ClientCapabilities.get -> Roslyn.LanguageServer.Protocol.ClientCapabilities! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.HotReloadRequestContext(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.IsTracking(Microsoft.CodeAnalysis.TextDocument! textDocument) -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.Solution.get -> Microsoft.CodeAnalysis.Solution? +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.TextDocument.get -> Microsoft.CodeAnalysis.TextDocument? +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Providers.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Register(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.RequestRefresh() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Unregister(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.DocumentId.get -> Microsoft.CodeAnalysis.DocumentId! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.IsDocument.get -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStartAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStopAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.InitializeAsync(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.RequestDataBridgeConnectionAsync(string! connectionId, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.HotReloadDiagnosticManager(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Providers.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Register(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.RequestRefresh() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Unregister(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetDocumentIdentifier() -> Roslyn.LanguageServer.Protocol.TextDocumentIdentifier? +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetId() -> Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.ProjectOrDocumentId +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetProject() -> Microsoft.CodeAnalysis.Project! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source, Microsoft.CodeAnalysis.TextDocument! textDocument) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.IsLiveSource() -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.ToDisplayString() -> string! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.HotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager, bool isDocument) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.IsDocument.get -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.IsEnabled(Roslyn.LanguageServer.Protocol.ClientCapabilities! clientCapabilities) -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.Name.get -> string! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry.RunningProcessEntry() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.CreateILspService(Microsoft.CodeAnalysis.LanguageServer.LspServices! lspServices, Microsoft.CodeAnalysis.LanguageServer.WellKnownLspServerKinds serverKind) -> Microsoft.CodeAnalysis.LanguageServer.ILspService! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.OnServiceBrokerInitialized(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.VisualDiagnosticsServiceFactory(Microsoft.CodeAnalysis.LanguageServer.LspWorkspaceRegistrationService! lspWorkspaceRegistrationService) -> void \ No newline at end of file diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj b/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj new file mode 100644 index 0000000000000..f63b387f65552 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj @@ -0,0 +1,35 @@ + + + + + Library + Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics + netstandard2.0 + + + true + Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics + + A supporting package for Visual Studio Microsoft.VisualStudio.DesignTools.CodeAnalysis.Diagnostics: + https://devdiv.visualstudio.com/DevDiv/_git/VS?path=/src/Xaml/Diagnostics/Source/CodeAnalysisDiagnostics + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/PublicAPI.Shipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/PublicAPI.Shipped.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/PublicAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs new file mode 100644 index 0000000000000..0b55c2089007d --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +internal interface IXamlDiagnosticSource +{ + Task> GetDiagnosticsAsync( + XamlRequestContext context, + CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs b/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs index e0f7cf9e77eda..812a79e593eef 100644 --- a/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs +++ b/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs @@ -73,6 +73,12 @@ public bool IsDynamicRegistrationSupported(string methodName) return _clientCapabilities?.Workspace?.DidChangeConfiguration?.DynamicRegistration == true; case LSP.Methods.WorkspaceDidChangeWatchedFilesName: return _clientCapabilities?.Workspace?.DidChangeWatchedFiles?.DynamicRegistration == true; + case LSP.VSInternalMethods.OnAutoInsertName: + if (_clientCapabilities.TextDocument is VSInternalTextDocumentClientCapabilities internalTextDocumentClientCapabilities) + { + return internalTextDocumentClientCapabilities.OnAutoInsert?.DynamicRegistration == true; + } + return false; default: throw new InvalidOperationException($"Unsupported dynamic registration method: {methodName}"); } diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs new file mode 100644 index 0000000000000..1cee9dbe1b173 --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +internal sealed class XamlDiagnosticSource(IXamlDiagnosticSource xamlDiagnosticSource, TextDocument document) : IDiagnosticSource +{ + bool IDiagnosticSource.IsLiveSource() => true; + Project IDiagnosticSource.GetProject() => document.Project; + ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id); + TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; + string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {document.FilePath ?? document.Name} in {document.Project.Name}"; + + async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + var xamlRequestContext = XamlRequestContext.FromRequestContext(context); + var diagnostics = await xamlDiagnosticSource.GetDiagnosticsAsync(xamlRequestContext, cancellationToken).ConfigureAwait(false); + var result = diagnostics.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); + return result; + } +} diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..526a26f5e57e8 --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.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; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class XamlDiagnosticSourceProvider([Import(AllowDefault = true)] IXamlDiagnosticSource? xamlDiagnosticSource) : IDiagnosticSourceProvider +{ + bool IDiagnosticSourceProvider.IsDocument => true; + + string IDiagnosticSourceProvider.Name => "XamlDiagnostics"; + + bool IDiagnosticSourceProvider.IsEnabled(ClientCapabilities clientCapabilities) => true; + + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (xamlDiagnosticSource != null && context.TextDocument is { } document && + document.Project.GetAdditionalDocument(document.Id) != null) + { + return new([new XamlDiagnosticSource(xamlDiagnosticSource, document)]); + } + + return new([]); + } +} diff --git a/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt index d62fe5bb29af1..c8d63468defae 100644 --- a/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt @@ -37,6 +37,8 @@ Microsoft.CodeAnalysis.ExternalAccess.Xaml.ILocationService.GetSymbolLocationsAs Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService.FromResolveData(object? resolveData) -> (object? data, System.Uri? uri) Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService.ToResolveData(object! data, System.Uri! uri) -> object! +Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler.HandleRequestAsync(TRequest request, Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler.MutatesSolutionState.get -> bool @@ -51,6 +53,10 @@ Microsoft.CodeAnalysis.ExternalAccess.Xaml.ResolveDataConversions Microsoft.CodeAnalysis.ExternalAccess.Xaml.StringConstants Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlCommandAttribute Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlCommandAttribute.XamlCommandAttribute(string! command) -> void +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSource.XamlDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource! xamlDiagnosticSource, Microsoft.CodeAnalysis.TextDocument! document) -> void +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSourceProvider.XamlDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource? xamlDiagnosticSource) -> void Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlMethodAttribute Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlMethodAttribute.XamlMethodAttribute(string! method) -> void Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext diff --git a/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs b/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs index 778a6dbe08607..290022941f109 100644 --- a/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs +++ b/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs @@ -61,13 +61,12 @@ public void GlobalSetup() "); - var connectionPoolService = _workspace.ExportProvider.GetExportedValue(); var asyncListener = _workspace.ExportProvider.GetExportedValue().GetListener(FeatureAttribute.PersistentStorage); - _storageService = new SQLitePersistentStorageService(connectionPoolService, new StorageConfiguration(), asyncListener); + _storageService = new SQLitePersistentStorageService(new StorageConfiguration(), asyncListener); var solution = _workspace.CurrentSolution; - _storage = _storageService.GetStorageWorkerAsync(SolutionKey.ToSolutionKey(solution), CancellationToken.None).AsTask().GetAwaiter().GetResult(); + _storage = _storageService.GetStorageAsync(SolutionKey.ToSolutionKey(solution), CancellationToken.None).AsTask().GetAwaiter().GetResult(); Console.WriteLine("Storage type: " + _storage.GetType()); _document = _workspace.CurrentSolution.Projects.Single().Documents.Single(); @@ -83,7 +82,6 @@ public void GlobalCleanup() } _document = null!; - _storage.Dispose(); _storage = null!; _storageService = null!; _workspace.Dispose(); diff --git a/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs index 7318e6a776139..bae7a906f62a3 100644 --- a/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs @@ -84,10 +84,8 @@ private async Task LoadSolutionAsync() if (storageService == null) throw new ArgumentException("Couldn't get storage service"); - using (var storage = await storageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), CancellationToken.None)) - { - Console.WriteLine("Sucessfully got persistent storage instance"); - } + var storage = await storageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), CancellationToken.None); + Console.WriteLine("Successfully got persistent storage instance"); // 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 diff --git a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs index be535cf145ab5..8b79dd838e9f9 100644 --- a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs @@ -183,24 +183,24 @@ public async Task RunFullParallelIndexing() Console.WriteLine("Starting indexing"); var storageService = _workspace.Services.SolutionServices.GetPersistentStorageService(); - using (var storage = await storageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), CancellationToken.None)) - { - Console.WriteLine("Successfully got persistent storage instance"); - var start = DateTime.Now; - var indexTime = TimeSpan.Zero; - var tasks = _workspace.CurrentSolution.Projects.SelectMany(p => p.Documents).Select(d => Task.Run( - async () => - { - var tree = await d.GetSyntaxRootAsync(); - var stopwatch = SharedStopwatch.StartNew(); - await TopLevelSyntaxTreeIndex.GetIndexAsync(d, default); - await SyntaxTreeIndex.GetIndexAsync(d, default); - indexTime += stopwatch.Elapsed; - })).ToList(); - await Task.WhenAll(tasks); - Console.WriteLine("Indexing time : " + indexTime); - Console.WriteLine("Solution parallel: " + (DateTime.Now - start)); - } + var storage = await storageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), CancellationToken.None); + + Console.WriteLine("Successfully got persistent storage instance"); + var start = DateTime.Now; + var indexTime = TimeSpan.Zero; + var tasks = _workspace.CurrentSolution.Projects.SelectMany(p => p.Documents).Select(d => Task.Run( + async () => + { + var tree = await d.GetSyntaxRootAsync(); + var stopwatch = SharedStopwatch.StartNew(); + await TopLevelSyntaxTreeIndex.GetIndexAsync(d, default); + await SyntaxTreeIndex.GetIndexAsync(d, default); + indexTime += stopwatch.Elapsed; + })).ToList(); + await Task.WhenAll(tasks); + Console.WriteLine("Indexing time : " + indexTime); + Console.WriteLine("Solution parallel: " + (DateTime.Now - start)); + Console.WriteLine("DB flushed"); Console.ReadLine(); } @@ -229,10 +229,10 @@ private async Task SearchAsync(Solution solution, IGrouping(); await service.SearchProjectsAsync( solution, grouping.ToImmutableArray(), priorityDocuments, "Syntax", service.KindsProvided, activeDocument: null, - (_, r) => + r => { lock (results) - results.Add(r); + results.AddRange(r); return Task.CompletedTask; }, diff --git a/src/Tools/Replay/Replay.cs b/src/Tools/Replay/Replay.cs index 60599f2dcdbf0..444b79364ae03 100644 --- a/src/Tools/Replay/Replay.cs +++ b/src/Tools/Replay/Replay.cs @@ -29,12 +29,14 @@ static ReplayOptions ParseOptions(string[] args) string? outputDirectory = null; string? binlogPath = null; int maxParallel = 6; + bool wait = false; var options = new Mono.Options.OptionSet() { { "b|binlogPath=", "The binary log to relpay", v => binlogPath = v }, { "o|outputDirectory=", "The directory to output the build results", v => outputDirectory = v }, { "m|maxParallel=", "The maximum number of parallel builds", (int v) => maxParallel = v }, + { "w|wait", "Wait for a key press after starting the server", o => wait = o is object }, }; var rest = options.Parse(args); @@ -44,7 +46,7 @@ static ReplayOptions ParseOptions(string[] args) } else if (rest.Count > 1) { - throw new Exception("Too many arguments"); + throw new Exception($"Too many arguments: {string.Join(" ", rest)}"); } if (string.IsNullOrEmpty(binlogPath)) @@ -62,7 +64,8 @@ static ReplayOptions ParseOptions(string[] args) clientDirectory: AppDomain.CurrentDomain.BaseDirectory!, outputDirectory: outputDirectory, binlogPath: binlogPath, - maxParallel: maxParallel); + maxParallel: maxParallel, + wait: wait); } static async Task RunAsync(ReplayOptions options) @@ -76,12 +79,20 @@ static async Task RunAsync(ReplayOptions options) Console.WriteLine("Starting server"); using var compilerServerLogger = new CompilerServerLogger("replay", Path.Combine(options.OutputDirectory, "server.log")); - if (!BuildServerConnection.TryCreateServer(options.ClientDirectory, options.PipeName, compilerServerLogger)) + if (!BuildServerConnection.TryCreateServer(options.ClientDirectory, options.PipeName, compilerServerLogger, out int serverProcessId)) { Console.WriteLine("Failed to start server"); return 1; } + Console.WriteLine($"Process Id: {serverProcessId}"); + if (options.Wait) + { + Console.WriteLine("Press any key to continue"); + Console.ReadKey(intercept: true); + Console.WriteLine("Continuing"); + } + try { var compilerCalls = ReadAllCompilerCalls(options.BinlogPath); @@ -257,7 +268,8 @@ internal sealed class ReplayOptions( string clientDirectory, string outputDirectory, string binlogPath, - int maxParallel) + int maxParallel, + bool wait) { internal string PipeName { get; } = pipeName; internal string ClientDirectory { get; } = clientDirectory; @@ -265,6 +277,7 @@ internal sealed class ReplayOptions( internal string BinlogPath { get; } = binlogPath; internal int MaxParallel { get; } = maxParallel; internal string TempDirectory { get; } = Path.Combine(outputDirectory, "temp"); + internal bool Wait { get; } = wait; } internal readonly struct BuildData(CompilerCall compilerCall, BuildResponse response) diff --git a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs new file mode 100644 index 0000000000000..1bfd842325f35 --- /dev/null +++ b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs @@ -0,0 +1,490 @@ +// Licensed to the .NET Foundation under one or more 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.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using System.Security.Cryptography; +using System.Text.RegularExpressions; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.CodeAnalysis.CSharp; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Tools; + +internal readonly record struct ApiPattern( + SymbolKindFlags SymbolKinds, + Regex MetadataNamePattern, + bool IsIncluded); + +[Flags] +internal enum SymbolKindFlags +{ + None = 0, + NamedType = 1, + Method = 1 << 1, + Field = 1 << 3, +} + +/// +/// The task transforms given assemblies by changing the visibility of members defined in these assemblies +/// based on filter patterns specified in the corresponding . +/// are text files whose file names (without extension) match the file names of . +/// Each API set specifies a list of patterns that define which members should be included or excluded from the output assembly. +/// All excluded members are made internal or private. +/// +public sealed class GenerateFilteredReferenceAssembliesTask : Task +{ + private static readonly Regex s_lineSyntax = new(""" + ^ + \s* + (?[+|-]?) + ((?[A-Za-z]+):)? + (?[^#]*) + ([#].*)? + $ + """, RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace); + + [Required] + public ITaskItem[] ApiSets { get; private set; } = null!; + + [Required] + public ITaskItem[] References { get; private set; } = null!; + + [Required] + public string OutputDir { get; private set; } = null!; + + public override bool Execute() + { + try + { + ExecuteImpl(); + } + catch (Exception e) + { + Log.LogError($"GenerateFilteredReferenceAssembliesTask failed with exception:{Environment.NewLine}{e}"); + } + + return !Log.HasLoggedErrors; + } + + private void ExecuteImpl() + { + ExecuteImpl(ApiSets.Select(item => (item.ItemSpec, (IReadOnlyList)File.ReadAllLines(item.ItemSpec)))); + } + + internal void ExecuteImpl(IEnumerable<(string apiSpecPath, IReadOnlyList lines)> apiSets) + { + var referencesByName = References.ToDictionary(r => Path.GetFileNameWithoutExtension(r.ItemSpec), r => r.ItemSpec); + + foreach (var (specPath, filters) in apiSets) + { + var assemblyName = Path.GetFileNameWithoutExtension(specPath); + if (!referencesByName.TryGetValue(assemblyName, out var originalReferencePath)) + { + Log.LogWarning($"Assembly '{assemblyName}' not found among project references"); + continue; + } + + var filteredReferencePath = Path.Combine(OutputDir, assemblyName + ".dll"); + var errors = new List<(string message, int line)>(); + var patterns = new List(); + ParseApiPatterns(filters, errors, patterns); + + foreach (var (message, line) in errors) + { + Log.LogWarning($"Invalid API pattern at {specPath} line {line}: {message}"); + } + + var peImageBuffer = File.ReadAllBytes(originalReferencePath); + Rewrite(peImageBuffer, patterns.ToImmutableArray()); + + try + { + File.WriteAllBytes(filteredReferencePath, peImageBuffer); + } + catch when (File.Exists(filteredReferencePath)) + { + // Another instance of the task might already be writing the content. + Log.LogMessage($"Output file '{filteredReferencePath}' already exists."); + } + } + } + + internal static void ParseApiPatterns(IReadOnlyList lines, List<(string message, int line)> errors, List patterns) + { + for (var i = 0; i < lines.Count; i++) + { + var line = lines[i]; + + var match = s_lineSyntax.Match(line); + if (!match.Success) + { + errors.Add(("unable to parse", i + 1)); + continue; + } + + var inclusion = match.Groups["Inclusion"].Value; + var kinds = match.Groups["Kinds"].Value; + var metadataName = match.Groups["MetadataName"].Value; + + var hasSymbolKindError = false; + var symbolKinds = SymbolKindFlags.None; + foreach (var kind in kinds) + { + symbolKinds |= kind switch + { + 'F' => SymbolKindFlags.Field, + 'M' => SymbolKindFlags.Method, + 'T' => SymbolKindFlags.NamedType, + _ => Unexpected() + }; + + SymbolKindFlags Unexpected() + { + hasSymbolKindError = true; + errors.Add(($"unexpected symbol kind: '{kind}'", i + 1)); + return SymbolKindFlags.None; + } + } + + if (hasSymbolKindError) + { + continue; + } + + if (symbolKinds == SymbolKindFlags.None) + { + symbolKinds = SymbolKindFlags.NamedType; + } + + if (metadataName is "") + { + if (inclusion is not "" || kinds is not "") + { + errors.Add(("expected metadata name", i + 1)); + } + + continue; + } + + patterns.Add(new() + { + SymbolKinds = symbolKinds, + MetadataNamePattern = ParseApiPattern(metadataName), + IsIncluded = inclusion is not ['-'] + }); + } + } + + /// + /// Interprets `*` as `.*` and escapes the rest of regex-special characters. + /// + internal static Regex ParseApiPattern(string pattern) + => new("^" + string.Join(".*", pattern.Trim().Split('*').Select(Regex.Escape)) + "$", + RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); + + internal static void GetAllMembers( + Compilation compilation, + List types, + List methods, + List fields) + { + Recurse(compilation.GlobalNamespace.GetMembers()); + + void Recurse(IEnumerable members) + { + foreach (var member in members) + { + switch (member) + { + case INamedTypeSymbol type: + if (type.MetadataToken != 0) + { + types.Add(type); + Recurse(type.GetMembers()); + } + break; + + case IMethodSymbol method: + if (method.MetadataToken != 0) + { + methods.Add(method); + } + break; + + case IFieldSymbol field: + if (field.MetadataToken != 0) + { + fields.Add(field); + } + break; + + case INamespaceSymbol ns: + Recurse(ns.GetMembers()); + break; + } + } + } + } + + private static bool IsIncluded(ISymbol symbol, ImmutableArray patterns) + { + var id = symbol.GetDocumentationCommentId(); + Debug.Assert(id is [_, ':', ..]); + id = id[2..]; + + var kind = GetKindFlags(symbol); + + // Type symbols areconsidered excluded by default. + // Member symbols are included by default since their type limits the effective visibility. + var isIncluded = symbol is not INamedTypeSymbol; + + foreach (var pattern in patterns) + { + if ((pattern.SymbolKinds & kind) == kind && pattern.MetadataNamePattern.IsMatch(id)) + { + isIncluded = pattern.IsIncluded; + } + } + + return isIncluded; + } + + private static SymbolKindFlags GetKindFlags(ISymbol symbol) + => symbol.Kind switch + { + SymbolKind.Field => SymbolKindFlags.Field, + SymbolKind.Method => SymbolKindFlags.Method, + SymbolKind.NamedType => SymbolKindFlags.NamedType, + _ => throw ExceptionUtilities.UnexpectedValue(symbol.Kind) + }; + + internal static unsafe void Rewrite(byte[] peImage, ImmutableArray patterns) + { + // Include all APIs if no patterns are specified. + if (patterns.IsEmpty) + { + return; + } + + using var readableStream = new MemoryStream(peImage, writable: false); + var metadataRef = MetadataReference.CreateFromStream(readableStream); + var compilation = CSharpCompilation.Create("Metadata", references: [metadataRef]); + + // Collect all member definitions that have visibility flags: + var types = new List(); + var methods = new List(); + var fields = new List(); + GetAllMembers(compilation, types, methods, fields); + + // Update visibility flags: + using var writableStream = new MemoryStream(peImage, writable: true); + using var peReader = new PEReader(writableStream); + using var writer = new BinaryWriter(writableStream); + + var headers = peReader.PEHeaders; + Debug.Assert(headers.PEHeader != null); + + var metadataReader = peReader.GetMetadataReader(); + var metadataOffset = peReader.PEHeaders.MetadataStartOffset; + + UpdateTypeDefinitions( + writer, + metadataReader, + patterns, + types.OrderBy(t => t.MetadataToken).ToImmutableArray(), + metadataOffset); + + UpdateMethodDefinitions( + writer, + metadataReader, + patterns, + methods.OrderBy(t => t.MetadataToken).ToImmutableArray(), + metadataOffset); + + UpdateFieldDefinitions( + writer, + metadataReader, + patterns, + fields.OrderBy(t => t.MetadataToken).ToImmutableArray(), + metadataOffset); + + // unsign: + if (headers.PEHeader.CertificateTableDirectory.Size > 0) + { + var certificateTableDirectoryOffset = (headers.PEHeader.Magic == PEMagic.PE32Plus) ? 144 : 128; + writableStream.Position = peReader.PEHeaders.PEHeaderStartOffset + certificateTableDirectoryOffset; + writer.Write((long)0); + } + + writer.Flush(); + + // update mvid: + var moduleDef = metadataReader.GetModuleDefinition(); + var mvidOffset = metadataOffset + metadataReader.GetHeapMetadataOffset(HeapIndex.Guid) + (MetadataTokens.GetHeapOffset(moduleDef.Mvid) - 1) * sizeof(Guid); +#if DEBUG + writableStream.Position = mvidOffset; + Debug.Assert(metadataReader.GetGuid(moduleDef.Mvid) == ReadGuid(writableStream)); +#endif + var newMvid = CreateMvid(writableStream); + writableStream.Position = mvidOffset; + WriteGuid(writer, newMvid); + + writer.Flush(); + } + + private static unsafe TSymbol? GetSymbolWithToken(ImmutableArray symbols, ref int symbolIndex, EntityHandle handle) where TSymbol : class, ISymbol + // If the current definition does not have corresponding symbol, + // we couldn't decode the symbol from metadata. Treat such definition as excluded. + => (symbolIndex < symbols.Length && symbols[symbolIndex].MetadataToken == MetadataTokens.GetToken(handle)) ? symbols[symbolIndex++] : null; + + private static unsafe void UpdateTypeDefinitions(BinaryWriter writer, MetadataReader metadataReader, ImmutableArray patterns, ImmutableArray symbols, int metadataOffset) + { + var tableOffset = metadataOffset + metadataReader.GetTableMetadataOffset(TableIndex.TypeDef); + var tableRowSize = metadataReader.GetTableRowSize(TableIndex.TypeDef); + var symbolIndex = 0; + + foreach (var handle in metadataReader.TypeDefinitions) + { + var symbol = GetSymbolWithToken(symbols, ref symbolIndex, handle); + if (symbol == null || !IsIncluded(symbol, patterns)) + { + var typeDef = metadataReader.GetTypeDefinition(handle); + + // reduce visibility so that the type is not visible outside the assembly: + var oldVisibility = typeDef.Attributes & TypeAttributes.VisibilityMask; + var newVisibility = oldVisibility switch + { + TypeAttributes.Public => TypeAttributes.NotPublic, + TypeAttributes.NestedPublic or TypeAttributes.NestedFamily or TypeAttributes.NestedFamORAssem => TypeAttributes.NestedAssembly, + _ => oldVisibility + }; + + if (oldVisibility == newVisibility) + { + continue; + } + + // Type attributes are store as the first field of the row and are 4B + var offset = tableOffset + (MetadataTokens.GetRowNumber(handle) - 1) * tableRowSize + 0; +#if DEBUG + writer.BaseStream.Position = offset; + Debug.Assert((TypeAttributes)ReadUInt32(writer.BaseStream) == typeDef.Attributes); +#endif + writer.BaseStream.Position = offset; + Debug.Assert(BitConverter.IsLittleEndian); + writer.Write((uint)(typeDef.Attributes & ~TypeAttributes.VisibilityMask | newVisibility)); + } + } + } + + private static unsafe void UpdateMethodDefinitions(BinaryWriter writer, MetadataReader metadataReader, ImmutableArray patterns, ImmutableArray symbols, int metadataOffset) + { + var tableOffset = metadataOffset + metadataReader.GetTableMetadataOffset(TableIndex.MethodDef); + var tableRowSize = metadataReader.GetTableRowSize(TableIndex.MethodDef); + var symbolIndex = 0; + + foreach (var handle in metadataReader.MethodDefinitions) + { + var symbol = GetSymbolWithToken(symbols, ref symbolIndex, handle); + if (symbol == null || !IsIncluded(symbol, patterns)) + { + var def = metadataReader.GetMethodDefinition(handle); + + // reduce visibility so that the method is not visible outside the assembly: + var oldVisibility = def.Attributes & MethodAttributes.MemberAccessMask; + var newVisibility = MethodAttributes.Private; + if (oldVisibility == newVisibility) + { + continue; + } + + // Row: RvaOffset (4B), ImplAttributes (2B), Attributes (2B), ... + var offset = tableOffset + (MetadataTokens.GetRowNumber(handle) - 1) * tableRowSize + sizeof(uint) + sizeof(ushort); +#if DEBUG + writer.BaseStream.Position = offset; + Debug.Assert((MethodAttributes)ReadUInt16(writer.BaseStream) == def.Attributes); +#endif + writer.BaseStream.Position = offset; + Debug.Assert(BitConverter.IsLittleEndian); + writer.Write((ushort)(def.Attributes & ~MethodAttributes.MemberAccessMask | newVisibility)); + } + } + } + + private static unsafe void UpdateFieldDefinitions(BinaryWriter writer, MetadataReader metadataReader, ImmutableArray patterns, ImmutableArray symbols, int metadataOffset) + { + var tableOffset = metadataOffset + metadataReader.GetTableMetadataOffset(TableIndex.Field); + var tableRowSize = metadataReader.GetTableRowSize(TableIndex.Field); + var symbolIndex = 0; + + foreach (var handle in metadataReader.FieldDefinitions) + { + var symbol = GetSymbolWithToken(symbols, ref symbolIndex, handle); + if (symbol == null || !IsIncluded(symbol, patterns)) + { + var def = metadataReader.GetFieldDefinition(handle); + + // reduce visibility so that the field is not visible outside the assembly: + var oldVisibility = def.Attributes & FieldAttributes.FieldAccessMask; + var newVisibility = FieldAttributes.Private; + if (oldVisibility == newVisibility) + { + continue; + } + + // Row: Attributes (2B), ... + var offset = tableOffset + (MetadataTokens.GetRowNumber(handle) - 1) * tableRowSize + 0; +#if DEBUG + writer.BaseStream.Position = offset; + Debug.Assert((FieldAttributes)ReadUInt16(writer.BaseStream) == def.Attributes); +#endif + writer.BaseStream.Position = offset; + Debug.Assert(BitConverter.IsLittleEndian); + writer.Write((ushort)(def.Attributes & ~FieldAttributes.FieldAccessMask | newVisibility)); + } + } + } + + private static uint ReadUInt32(Stream stream) + => unchecked((uint)(stream.ReadByte() | stream.ReadByte() << 8 | stream.ReadByte() << 16 | stream.ReadByte() << 24)); + + private static uint ReadUInt16(Stream stream) + => unchecked((uint)(stream.ReadByte() | stream.ReadByte() << 8)); + + private static unsafe Guid ReadGuid(Stream stream) + { + var buffer = new byte[sizeof(Guid)]; + Debug.Assert(stream.Read(buffer, 0, buffer.Length) == buffer.Length); + fixed (byte* ptr = buffer) + { + var reader = new BlobReader(ptr, buffer.Length); + return reader.ReadGuid(); + } + } + + private static unsafe void WriteGuid(BinaryWriter writer, Guid guid) + { + var buffer = new byte[sizeof(Guid)]; + var blob = new BlobWriter(buffer); + blob.WriteGuid(guid); + writer.Write(buffer, 0, buffer.Length); + } + + private static Guid CreateMvid(Stream stream) + { + stream.Position = 0; + using var sha = SHA256.Create(); + return BlobContentId.FromHash(sha.ComputeHash(stream)).Guid; + } +} diff --git a/src/Tools/SemanticSearch/BuildTask/SemanticSearch.BuildTask.csproj b/src/Tools/SemanticSearch/BuildTask/SemanticSearch.BuildTask.csproj new file mode 100644 index 0000000000000..9e28511dab63f --- /dev/null +++ b/src/Tools/SemanticSearch/BuildTask/SemanticSearch.BuildTask.csproj @@ -0,0 +1,26 @@ + + + Library + $(NetRoslyn);net472 + true + true + + + + + + + + + + + + + + + + + + + + diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/Microsoft.CodeAnalysis.CSharp.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/Microsoft.CodeAnalysis.CSharp.txt new file mode 100644 index 0000000000000..5f282702bb03e --- /dev/null +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/Microsoft.CodeAnalysis.CSharp.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/Microsoft.CodeAnalysis.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/Microsoft.CodeAnalysis.txt new file mode 100644 index 0000000000000..6d657b0290b8a --- /dev/null +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/Microsoft.CodeAnalysis.txt @@ -0,0 +1,5 @@ ++T:* +-M:Microsoft.CodeAnalysis.MetadataReference.CreateFromFile* +-T:Microsoft.CodeAnalysis.FileSystemExtensions +-T:Microsoft.CodeAnalysis.CommandLineParser +-T:Microsoft.CodeAnalysis.DesktopStrongNameProvider \ No newline at end of file diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/System.Collections.Immutable.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/System.Collections.Immutable.txt new file mode 100644 index 0000000000000..5f282702bb03e --- /dev/null +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/System.Collections.Immutable.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/System.Collections.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/System.Collections.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/System.Linq.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/System.Linq.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/System.Runtime.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/System.Runtime.txt new file mode 100644 index 0000000000000..ce07ea552448d --- /dev/null +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/ApiSet/System.Runtime.txt @@ -0,0 +1,61 @@ ++T:System.* +-T:System.Activator +-T:System.AppDomain* +-T:System.AssemblyLoad* +-T:System.AppContext +-TM:System.Environment.* ++M:System.Environment.get_CurrentManagedThreadId ++M:System.Environment.get_NewLine +-T:System.EnvironmentVariableTarget +-T:System.GC* +-T:System.LoaderOptimization* +-T:System.MarshalByRefObject +-T:System.MTAThreadAttribute +-T:System.STAThreadAttribute +-T:System.ThreadStaticAttribute +-T:System.Diagnostics.Debugger +-M:System.Globalization.CultureInfo.set_* +-M:System.Type.* ++M:System.Type.Name ++M:System.Type.FullName ++M:System.Type.AssemblyQualifiedName +-T:System.IO.* ++T:System.IO.BinaryReader ++T:System.IO.BinaryWriter ++T:System.IO.BufferedStream ++T:System.IO.EndOfStreamException ++T:System.IO.InvalidDataException ++T:System.IO.MemoryStream ++T:System.IO.Stream ++T:System.IO.StreamReader ++T:System.IO.StreamWriter ++T:System.IO.StringReader ++T:System.IO.StringWriter ++T:System.IO.TextReader ++T:System.IO.TextWriter + +-T:System.Net.* +-T:System.Reflection.* +-T:System.Resources.* + +-T:System.Runtime.* ++T:System.Runtime.CompilerServices.* + +-T:System.Security.* + +-T:System.Threading.* ++T:System.Threading.CancellationToken + ++T:System.Threading.Tasks.* +-M:System.Threading.Tasks.Task.* +-M:System.Threading.Tasks.Task`1.* ++M:System.Threading.Tasks.Task*.get_* ++M:System.Threading.Tasks.Task*.ConfigureAwait(*) ++M:System.Threading.Tasks.Task*.GetAwaiter ++M:System.Threading.Tasks.Task*.From*(*) ++M:System.Threading.Tasks.Task*.Dispose* +-T:System.Threading.Tasks.TaskFactory* +-T:System.Threading.Tasks.TaskScheduler + ++T:System.Threading.Tasks.ConfigureAwaitOptions ++T:System.Threading.Tasks.TaskAsyncEnumerableExtensions \ No newline at end of file diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/SemanticSearch.ReferenceAssemblies.csproj b/src/Tools/SemanticSearch/ReferenceAssemblies/SemanticSearch.ReferenceAssemblies.csproj new file mode 100644 index 0000000000000..0ff3c9cb5533c --- /dev/null +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/SemanticSearch.ReferenceAssemblies.csproj @@ -0,0 +1,69 @@ + + + Library + $(NetRoslyn) + + <_BuildTaskTfm Condition="'$(MSBuildRuntimeType)' != 'Core'">net472 + <_BuildTaskTfm Condition="'$(MSBuildRuntimeType)' == 'Core'">$(NetRoslyn) + + + + + + + + + + + + + + + + + + <_OutputDir>$(IntermediateOutputPath)GeneratedRefAssemblies\ + + + + <_InputReference Include="@(ReferencePath)" + Condition="'%(ReferencePath.FrameworkReferenceName)' == 'Microsoft.NETCore.App' or + '%(ReferencePath.FileName)' == 'System.Collections.Immutable' or + '%(ReferencePath.FileName)' == 'Microsoft.CodeAnalysis' or + '%(ReferencePath.FileName)' == 'Microsoft.CodeAnalysis.CSharp'" /> + + + + <_InputFile Include="@(ApiSet)" /> + <_InputFile Include="@(_InputReference)" /> + + <_OutputFile Include="@(ApiSet->'$(_OutputDir)%(FileName).dll')" /> + + + + + + + + + + + + + + + + + + <_PublishProjectOutputGroupOutput Include="@(_OutputFile)" /> + + + diff --git a/src/Tools/SemanticSearch/Tests/GenerateFilteredReferenceAssembliesTaskTests.cs b/src/Tools/SemanticSearch/Tests/GenerateFilteredReferenceAssembliesTaskTests.cs new file mode 100644 index 0000000000000..9169590df99ff --- /dev/null +++ b/src/Tools/SemanticSearch/Tests/GenerateFilteredReferenceAssembliesTaskTests.cs @@ -0,0 +1,247 @@ +// Licensed to the .NET Foundation under one or more 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 System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Tools.UnitTests; + +public sealed class GenerateFilteredReferenceAssembliesTaskTests : CSharpTestBase +{ + [Theory] + [InlineData("")] + [InlineData("\t")] + [InlineData("\t#\t")] + [InlineData("#")] + [InlineData("#+")] + [InlineData("#abc")] + [InlineData("#𫚭鿯龻蝌灋齅ㄥ﹫䶱ན།ىي꓂")] // GB18030 + public void ParseApiPatterns_Empty(string value) + { + var errors = new List<(string message, int line)>(); + var patterns = new List(); + GenerateFilteredReferenceAssembliesTask.ParseApiPatterns([value], errors, patterns); + Assert.Empty(errors); + Assert.Empty(patterns); + } + + [Theory] + [InlineData("+")] + [InlineData("-")] + [InlineData("-#")] + public void ParseApiPatterns_Error_ExpectedMetadataName(string value) + { + var errors = new List<(string message, int line)>(); + var patterns = new List(); + GenerateFilteredReferenceAssembliesTask.ParseApiPatterns([value], errors, patterns); + AssertEx.Equal(new[] { ("expected metadata name", 1) }, errors); + Assert.Empty(patterns); + } + + [Theory] + [InlineData("E")] + [InlineData("P")] + [InlineData("N")] + [InlineData("X")] + internal void ParseApiPatterns_Error_UnexpectedSymbolKind(string kind) + { + var errors = new List<(string message, int line)>(); + var patterns = new List(); + GenerateFilteredReferenceAssembliesTask.ParseApiPatterns([kind + ":*"], errors, patterns); + AssertEx.Equal(new[] { ($"unexpected symbol kind: '{kind}'", 1) }, errors); + Assert.Empty(patterns); + } + + [Theory] + [InlineData("*", true, SymbolKindFlags.NamedType, @".*")] + [InlineData("?", true, SymbolKindFlags.NamedType, @"\?")] + [InlineData("%", true, SymbolKindFlags.NamedType, @"%")] + [InlineData("<", true, SymbolKindFlags.NamedType, @"<")] + [InlineData("a b c", true, SymbolKindFlags.NamedType, @"a\ b\ c")] + [InlineData("a b c#", true, SymbolKindFlags.NamedType, @"a\ b\ c")] + [InlineData(" a b #c", true, SymbolKindFlags.NamedType, @"a\ b")] + [InlineData(" + System.IO", true, SymbolKindFlags.NamedType, @"System\.IO")] + [InlineData("+System.IO.*", true, SymbolKindFlags.NamedType, @"System\.IO\..*")] + [InlineData(" -System.IO.**", false, SymbolKindFlags.NamedType, @"System\.IO\..*.*")] + [InlineData("- System.IO.* *", false, SymbolKindFlags.NamedType, @"System\.IO\..*\ .*")] + [InlineData("𫚭鿯龻蝌灋齅ㄥ﹫䶱ན།ىي꓂", true, SymbolKindFlags.NamedType, @"𫚭鿯龻蝌灋齅ㄥ﹫䶱ན།ىي꓂")] // GB18030 + [InlineData("M:*", true, SymbolKindFlags.Method, @".*")] + [InlineData("M:?", true, SymbolKindFlags.Method, @"\?")] + [InlineData("M: a b #c", true, SymbolKindFlags.Method, @"a\ b")] + [InlineData("+M: System.IO", true, SymbolKindFlags.Method, @"System\.IO")] + [InlineData("+M: System.IO.Path.F(*)", true, SymbolKindFlags.Method, @"System\.IO\.Path\.F\(.*\)")] + internal void ParseApiPatterns(string value, bool isIncluded, SymbolKindFlags symbolKinds, string pattern) + { + var errors = new List<(string message, int line)>(); + var patterns = new List(); + GenerateFilteredReferenceAssembliesTask.ParseApiPatterns([value], errors, patterns); + Assert.Empty(errors); + + AssertEx.Equal(new[] { (symbolKinds, $"^{pattern}$", isIncluded) }, patterns.Select(p => (p.SymbolKinds, p.MetadataNamePattern.ToString(), p.IsIncluded))); + } + + [Fact] + public void Types() + { + var libSource = CreateCompilation(""" + namespace N + { + public class C + { + public class D; + } + } + + namespace M + { + public class E; + public class E; + public class E; + } + """); + + var dir = Temp.CreateDirectory(); + var libImage = libSource.EmitToArray(new EmitOptions(metadataOnly: true)).ToArray(); + + var patterns = ImmutableArray.Create( + new ApiPattern(SymbolKindFlags.NamedType, new Regex(@"M\.E.*"), IsIncluded: true), + new ApiPattern(SymbolKindFlags.NamedType, new Regex(@"M\.E`1"), IsIncluded: false)); + + GenerateFilteredReferenceAssembliesTask.Rewrite(libImage, patterns); + + var libRef = MetadataReference.CreateFromImage(libImage); + + var c = CreateCompilation(""" + N.C.D d = null; + M.E e1 = null; + M.E e2 = null; + M.E e3 = null; + """, references: [libRef], TestOptions.DebugExe); + + c.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Verify( + // (1,3): error CS0122: 'C' is inaccessible due to its protection level + // N.C.D d = null; + Diagnostic(ErrorCode.ERR_BadAccess, "C").WithArguments("N.C").WithLocation(1, 3), + // (3,3): error CS0122: 'E' is inaccessible due to its protection level + // M.E e2 = null; + Diagnostic(ErrorCode.ERR_BadAccess, "E").WithArguments("M.E").WithLocation(3, 3)); + } + + [Fact] + public void Interface() + { + var libSource = CreateCompilation(""" + public interface I + { + public void M1(); + public void M2(); + } + + public class C : I + { + public C() => throw null; + public void M1() => throw null; + public void M2() => throw null; + } + """); + + var dir = Temp.CreateDirectory(); + var libImage = libSource.EmitToArray(new EmitOptions(metadataOnly: true)).ToArray(); + + var patterns = ImmutableArray.Create( + new ApiPattern(SymbolKindFlags.NamedType, new Regex(@".*"), IsIncluded: true), + new ApiPattern(SymbolKindFlags.Method, new Regex(@"I.M1"), IsIncluded: false)); + + GenerateFilteredReferenceAssembliesTask.Rewrite(libImage, patterns); + + var libRef = MetadataReference.CreateFromImage(libImage); + + var c = CreateCompilation(""" + I i = new C(); + i.M1(); + i.M2(); + + C c = new C(); + c.M1(); + c.M2(); + """, references: [libRef], TestOptions.DebugExe); + + c.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Verify( + // (2,3): error CS0122: 'I.M1()' is inaccessible due to its protection level + // i.M1(); + Diagnostic(ErrorCode.ERR_BadAccess, "M1").WithArguments("I.M1()").WithLocation(2, 3)); + } + + [Fact] + public void Property() + { + var libSource = CreateCompilation(""" + public class C + { + public int P1 { get; } + public int P2 { get; set; } + public int P3 { get; protected set; } + public int P4 { get; private protected set; } + } + """); + + var dir = Temp.CreateDirectory(); + var libImage = libSource.EmitToArray(new EmitOptions(metadataOnly: true)).ToArray(); + + var patterns = ImmutableArray.Create( + new ApiPattern(SymbolKindFlags.NamedType, new Regex(@".*"), IsIncluded: true), + new ApiPattern(SymbolKindFlags.Method, new Regex(@"C\.get_.*"), IsIncluded: false), + new ApiPattern(SymbolKindFlags.Method, new Regex(@"C\.set_.*"), IsIncluded: false), + new ApiPattern(SymbolKindFlags.Method, new Regex(@"C\.get_P2"), IsIncluded: true)); + + GenerateFilteredReferenceAssembliesTask.Rewrite(libImage, patterns); + + var libRef = MetadataReference.CreateFromImage(libImage); + + var c = CreateCompilation(""" + var d = new D(); + d.F(); + + class D : C + { + public int F() + { + P2 = 2; + P3 = 3; + P4 = 4; + + return P1 + P2 + P3 + P4; + } + } + """, references: [libRef], TestOptions.DebugExe); + + c.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).Verify( + // (8,9): error CS0200: Property or indexer 'C.P2' cannot be assigned to -- it is read only + // P2 = 2; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P2").WithArguments("C.P2").WithLocation(8, 9), + // (9,9): error CS0103: The name 'P3' does not exist in the current context + // P3 = 3; + Diagnostic(ErrorCode.ERR_NameNotInContext, "P3").WithArguments("P3").WithLocation(9, 9), + // (10,9): error CS0103: The name 'P4' does not exist in the current context + // P4 = 4; + Diagnostic(ErrorCode.ERR_NameNotInContext, "P4").WithArguments("P4").WithLocation(10, 9), + // (12,16): error CS0103: The name 'P1' does not exist in the current context + // return P1 + P2 + P3 + P4; + Diagnostic(ErrorCode.ERR_NameNotInContext, "P1").WithArguments("P1").WithLocation(12, 16), + // (12,26): error CS0103: The name 'P3' does not exist in the current context + // return P1 + P2 + P3 + P4; + Diagnostic(ErrorCode.ERR_NameNotInContext, "P3").WithArguments("P3").WithLocation(12, 26), + // (12,31): error CS0103: The name 'P4' does not exist in the current context + // return P1 + P2 + P3 + P4; + Diagnostic(ErrorCode.ERR_NameNotInContext, "P4").WithArguments("P4").WithLocation(12, 31)); + } +} diff --git a/src/Tools/SemanticSearch/Tests/SemanticSearch.BuildTask.UnitTests.csproj b/src/Tools/SemanticSearch/Tests/SemanticSearch.BuildTask.UnitTests.csproj new file mode 100644 index 0000000000000..82d137d640c9c --- /dev/null +++ b/src/Tools/SemanticSearch/Tests/SemanticSearch.BuildTask.UnitTests.csproj @@ -0,0 +1,11 @@ + + + Library + $(NetRoslyn);net472 + + + + + + + diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.cs.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.cs.xlf index 690ce89eb11d0..af4d846710f81 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.cs.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.cs.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# Interactive C# Semantic Search - C# Semantic Search + C# Sémantické vyhledávání Initialize Interactive with Project - Initialize Interactive with Project + Inicializovat Interactive přes projekt diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.de.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.de.xlf index 7b7cccfec5b52..abe0d2cc230d5 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.de.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.de.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# Interactive C# Semantic Search - C# Semantic Search + Semantische C#-Suche Initialize Interactive with Project - Initialize Interactive with Project + Interaktiv mit dem Projekt initialisieren diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.es.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.es.xlf index 9b962e84d5f16..119e1eaf90037 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.es.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.es.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# interactivo C# Semantic Search - C# Semantic Search + Búsqueda semántica de C# Initialize Interactive with Project - Initialize Interactive with Project + Inicializar el elemento interactivo con el proyecto diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.fr.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.fr.xlf index 6dae65fe61cbf..fd9b03fce2517 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.fr.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.fr.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# Interactive C# Semantic Search - C# Semantic Search + Recherche sémantique C# Initialize Interactive with Project - Initialize Interactive with Project + Initialiser Interactive avec le projet diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.it.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.it.xlf index ededa633cd4d3..6bca98519df69 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.it.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.it.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# Interactive C# Semantic Search - C# Semantic Search + Ricerca semantica C# Initialize Interactive with Project - Initialize Interactive with Project + Inizializza Interactive con il progetto diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ja.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ja.xlf index 6b3b47991c93d..c7aeecc25419f 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ja.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ja.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# インタラクティブ C# Semantic Search - C# Semantic Search + C# セマンティック検索 Initialize Interactive with Project - Initialize Interactive with Project + プロジェクトでインタラクティブを初期化 diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ko.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ko.xlf index 2ebb6cb7a2b11..fad466b8aaffa 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ko.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ko.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# 대화형 C# Semantic Search - C# Semantic Search + C# 의미 체계 검색 Initialize Interactive with Project - Initialize Interactive with Project + 프로젝트에서 Interactive 초기화 diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.pl.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.pl.xlf index 1829759bfe220..49dad14d012f0 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.pl.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.pl.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# Interactive C# Semantic Search - C# Semantic Search + Wyszukiwanie semantyczne C# Initialize Interactive with Project - Initialize Interactive with Project + Inicjuj środowisko interaktywne z projektem diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.pt-BR.xlf index 174a159ee952c..c67e3046086da 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.pt-BR.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.pt-BR.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# Interativo C# Semantic Search - C# Semantic Search + Pesquisa semântica do C# Initialize Interactive with Project - Initialize Interactive with Project + Inicializar Interativo com o Projeto diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ru.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ru.xlf index 03fb7affc7311..99f11773333c6 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ru.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.ru.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# Interactive C# Semantic Search - C# Semantic Search + Семантический поиск C# Initialize Interactive with Project - Initialize Interactive with Project + Инициализировать интерактивное окно с проектом diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.tr.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.tr.xlf index b4d9c1193c701..5e5c9ef2e789c 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.tr.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.tr.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# Etkileşimli C# Semantic Search - C# Semantic Search + C# Anlamsal Arama Initialize Interactive with Project - Initialize Interactive with Project + Projeyi Etkileşimli Pencerede Başlat diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.zh-Hans.xlf index c34d2b5c61d76..e9b8dff57c8cb 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.zh-Hans.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.zh-Hans.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# 交互窗口 C# Semantic Search - C# Semantic Search + C# 语义搜索 Initialize Interactive with Project - Initialize Interactive with Project + 对交互窗口进行项目初始化 diff --git a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.zh-Hant.xlf index 7b0bf4cdbfba8..bfd19c743cf32 100644 --- a/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.zh-Hant.xlf +++ b/src/VisualStudio/CSharp/Impl/.vsextension/xlf/string-resources.json.zh-Hant.xlf @@ -4,17 +4,17 @@ C# Interactive - C# Interactive + C# 互動 C# Semantic Search - C# Semantic Search + C# 語意搜尋 Initialize Interactive with Project - Initialize Interactive with Project + 使用專案將 Interactive 初始化 diff --git a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs index ccae4e77195f6..30bfc38cee381 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs @@ -65,7 +65,6 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke try { await base.InitializeAsync(cancellationToken, progress).ConfigureAwait(true); - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); this.RegisterService(async ct => diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx index 4d5832f4d4f0b..e3b595303567f 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx @@ -637,4 +637,19 @@ Allow blank line after token in arrow expression clause + + Semantic Search + + + Run Query + + + Cancel Query + + + Run (Ctrl+Enter) + + + Cancel (Escape) + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs b/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs index 450ff38f04e2f..1453ad36fe7dc 100644 --- a/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs +++ b/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs @@ -47,7 +47,7 @@ public override SymbolDisplayPart[] GeneratePreviewDisplayParts(AddedParameterVi parts.Add(new SymbolDisplayPart(SymbolDisplayPartKind.Text, null, addedParameterViewModel.Default)); } - return parts.ToArray(); + return [.. parts]; } // Use LangVersion Preview to ensure that all types parse correctly. If the user types in a type only available diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs index f60c857bfe11e..b89bccb0e4de9 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs @@ -35,6 +35,8 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.CodeModel { + using static CSharpSyntaxTokens; + internal partial class CSharpCodeModelService : AbstractCodeModelService { private static readonly SyntaxTree s_emptyTree = SyntaxFactory.ParseSyntaxTree(SourceText.From("", encoding: null, SourceHashAlgorithms.Default)); @@ -275,17 +277,15 @@ public override bool MatchesScope(SyntaxNode node, EnvDTE.vsCMElement scope) } public override IEnumerable GetOptionNodes(SyntaxNode parent) - { // Only VB has Option statements - return SpecializedCollections.EmptyEnumerable(); - } + => []; public override IEnumerable GetImportNodes(SyntaxNode parent) => parent switch { CompilationUnitSyntax compilationUnit => compilationUnit.Usings, BaseNamespaceDeclarationSyntax baseNamespace => baseNamespace.Usings, - _ => SpecializedCollections.EmptyEnumerable(), + _ => [], }; private static IEnumerable GetAttributeNodes(SyntaxList attributeDeclarationList) @@ -343,7 +343,7 @@ public override IEnumerable GetAttributeNodes(SyntaxNode parent) return GetAttributeNodes(accessor.AttributeLists); } - return SpecializedCollections.EmptyEnumerable(); + return []; } public override IEnumerable GetAttributeArgumentNodes(SyntaxNode parent) @@ -351,26 +351,24 @@ public override IEnumerable GetAttributeArgumentNodes(SyntaxNode par if (parent is AttributeSyntax attribute) { if (attribute.ArgumentList == null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return attribute.ArgumentList.Arguments; } - return SpecializedCollections.EmptyEnumerable(); + return []; } public override IEnumerable GetInheritsNodes(SyntaxNode parent) { // Only VB has Inherits statements - return SpecializedCollections.EmptyEnumerable(); + return []; } public override IEnumerable GetImplementsNodes(SyntaxNode parent) { // Only VB has Implements statements - return SpecializedCollections.EmptyEnumerable(); + return []; } private static bool IsContainerNode(SyntaxNode container) @@ -1465,7 +1463,7 @@ public override IEnumerable GetParameterNodes(SyntaxNode parentNode) return delegateDecl.ParameterList.Parameters; } - return SpecializedCollections.EmptyEnumerable(); + return []; } public override bool IsExpressionBodiedProperty(SyntaxNode node) @@ -1916,11 +1914,11 @@ public override SyntaxNode SetParameterKind(SyntaxNode node, EnvDTE80.vsCMParame switch (kind) { case EnvDTE80.vsCMParameterKind.vsCMParameterKindOut: - newModifiers = [SyntaxFactory.Token(SyntaxKind.OutKeyword)]; + newModifiers = [OutKeyword]; break; case EnvDTE80.vsCMParameterKind.vsCMParameterKindRef: - newModifiers = [SyntaxFactory.Token(SyntaxKind.RefKeyword)]; + newModifiers = [RefKeyword]; break; case EnvDTE80.vsCMParameterKind.vsCMParameterKindIn: @@ -1934,7 +1932,7 @@ public override SyntaxNode SetParameterKind(SyntaxNode node, EnvDTE80.vsCMParame if (parameterList.Parameters.LastOrDefault() == parameter && parameter.Type is ArrayTypeSyntax) { - newModifiers = [SyntaxFactory.Token(SyntaxKind.ParamsKeyword)]; + newModifiers = [ParamsKeyword]; break; } diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService_Prototype.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService_Prototype.cs index 888846f199862..ae6c558d1d15c 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService_Prototype.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService_Prototype.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; @@ -554,7 +555,7 @@ private void AppendParameterPrototype(StringBuilder builder, PrototypeFlags flag private static void AppendTypeNamePrototype(StringBuilder builder, bool includeNamespaces, bool includeGenerics, ISymbol symbol) { - var symbols = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var symbols); while (symbol != null) { @@ -572,10 +573,8 @@ private static void AppendTypeNamePrototype(StringBuilder builder, bool includeN } var first = true; - while (symbols.Count > 0) + while (symbols.TryPop(out var current)) { - var current = symbols.Pop(); - if (!first) { builder.Append('.'); diff --git a/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs b/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs index d2a6585c92a74..c6575aafcd670 100644 --- a/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs +++ b/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs @@ -8,7 +8,6 @@ using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data; using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider; using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater; @@ -84,6 +83,7 @@ private static IEnumerable GetNullCheckingCodeStyleOptions(Tie private static IEnumerable GetModifierCodeStyleOptions(TieredAnalyzerConfigOptions options, OptionUpdater updater) { yield return CodeStyleSetting.Create(CSharpCodeStyleOptions.PreferStaticLocalFunction, ServicesVSResources.Prefer_static_local_functions, options, updater); + yield return CodeStyleSetting.Create(CSharpCodeStyleOptions.PreferStaticAnonymousFunction, ServicesVSResources.Prefer_static_anonymous_functions, options, updater); yield return CodeStyleSetting.Create(CSharpCodeStyleOptions.PreferReadOnlyStruct, ServicesVSResources.Prefer_read_only_struct, options, updater); yield return CodeStyleSetting.Create(CSharpCodeStyleOptions.PreferReadOnlyStructMember, ServicesVSResources.Prefer_read_only_struct_member, options, updater); } diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml index e1416d98bc310..5ce325804d331 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml @@ -53,16 +53,7 @@ - - - - - + Content="{x:Static local:AdvancedOptionPageStrings.Option_run_code_analysis_in_separate_process}" /> @@ -74,7 +65,34 @@ Content="{x:Static local:AdvancedOptionPageStrings.Option_Skip_analyzers_for_implicitly_triggered_builds}" /> - + + + + + + + + + + + + + + @@ -187,6 +205,8 @@ Content="{x:Static local:AdvancedOptionPageStrings.Option_Show_guides_for_declaration_level_constructs}" /> + @@ -218,8 +238,8 @@ Content="{x:Static local:AdvancedOptionPageStrings.Option_Report_invalid_placeholders_in_string_dot_format_calls}" /> - + diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs index 859461ef020cb..7cdd7d89ef7b0 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs @@ -67,13 +67,6 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon BindToOption(on_the_right_edge_of_the_editor_window, InlineDiagnosticsOptionsStorage.Location, InlineDiagnosticsLocations.PlacedAtEndOfEditor, LanguageNames.CSharp); BindToOption(Run_code_analysis_in_separate_process, RemoteHostOptionsStorage.OOP64Bit); - BindToOption(Run_code_analysis_on_dotnet, RemoteHostOptionsStorage.OOPCoreClr); - - BindToOption(Analyze_source_generated_files, SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles, () => - { - // If the option has not been set by the user, check if the option is enabled from experimentation. If so, default to that. - return optionStore.GetOption(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFilesFeatureFlag); - }); BindToOption(Enable_file_logging_for_diagnostics, VisualStudioLoggingOptionsStorage.EnableFileLoggingForDiagnostics); BindToOption(Skip_analyzers_for_implicitly_triggered_builds, FeatureOnOffOptions.SkipAnalyzersForImplicitlyTriggeredBuilds); @@ -84,6 +77,31 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon return optionStore.GetOption(FeatureOnOffOptions.OfferRemoveUnusedReferencesFeatureFlag); }); + // Source Generators + BindToOption(Automatic_Run_generators_after_any_change, WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic, () => + { + // If the option hasn't been set by the user, then check the feature flag. If the feature flag has set + // us to only run when builds complete, then we're not in automatic mode. So we `!` the result. + return !optionStore.GetOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecutionBalancedFeatureFlag); + }); + BindToOption(Balanced_Run_generators_after_saving_or_building, WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Balanced, () => + { + // If the option hasn't been set by the user, then check the feature flag. If the feature flag has set + // us to only run when builds complete, then we're in `Balanced_Run_generators_after_saving_or_building` mode and directly return it. + return optionStore.GetOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecutionBalancedFeatureFlag); + }); + BindToOption(Analyze_source_generated_files, SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles, () => + { + // If the option has not been set by the user, check if the option is enabled from experimentation. If so, default to that. + return optionStore.GetOption(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFilesFeatureFlag); + }); + BindToOption(Enable_all_features_in_opened_files_from_source_generators, WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspace, () => + { + // If the option has not been set by the user, check if the option is enabled from experimentation. + // If so, default to that. + return optionStore.GetOption(WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag); + }); + // Go To Definition BindToOption(Enable_navigation_to_sourcelink_and_embedded_sources, MetadataAsSourceOptionsStorage.NavigateToSourceLinkAndEmbeddedSources); BindToOption(Enable_navigation_to_decompiled_sources, MetadataAsSourceOptionsStorage.NavigateToDecompiledSources); @@ -124,6 +142,7 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon // Block Structure Guides BindToOption(Show_guides_for_declaration_level_constructs, BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp); BindToOption(Show_guides_for_code_level_constructs, BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp); + BindToOption(Show_guides_for_comments_and_preprocessor_regions, BlockStructureOptionsStorage.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, LanguageNames.CSharp); // Comments BindToOption(GenerateXmlDocCommentsForTripleSlash, DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, LanguageNames.CSharp); @@ -137,12 +156,7 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon BindToOption(Fix_text_pasted_into_string_literals_experimental, StringCopyPasteOptionsStorage.AutomaticallyFixStringContentsOnPaste, LanguageNames.CSharp); BindToOption(Report_invalid_placeholders_in_string_dot_format_calls, IdeAnalyzerOptionsStorage.ReportInvalidPlaceholdersInStringDotFormatCalls, LanguageNames.CSharp); BindToOption(Underline_reassigned_variables, ClassificationOptionsStorage.ClassifyReassignedVariables, LanguageNames.CSharp); - BindToOption(Enable_all_features_in_opened_files_from_source_generators, WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspace, () => - { - // If the option has not been set by the user, check if the option is enabled from experimentation. - // If so, default to that. - return optionStore.GetOption(WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag); - }); + BindToOption(Strike_out_obsolete_symbols, ClassificationOptionsStorage.ClassifyObsoleteSymbols, LanguageNames.CSharp); // Regular Expressions BindToOption(Colorize_regular_expressions, ClassificationOptionsStorage.ColorizeRegexPatterns, LanguageNames.CSharp); @@ -277,15 +291,5 @@ private void EnterOutliningMode_Unchecked(object sender, RoutedEventArgs e) Collapse_metadata_signature_files_on_open.IsEnabled = false; Collapse_sourcelink_embedded_decompiled_files_on_open.IsEnabled = false; } - - private void Run_code_analysis_in_separate_process_Checked(object sender, RoutedEventArgs e) - { - Run_code_analysis_on_dotnet.IsEnabled = true; - } - - private void Run_code_analysis_in_separate_process_Unchecked(object sender, RoutedEventArgs e) - { - Run_code_analysis_on_dotnet.IsEnabled = false; - } } } diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs index 2f53e9718508f..329ae543c94ac 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs @@ -78,9 +78,6 @@ public static string Option_Always_use_default_symbol_servers_for_navigation public static string Option_run_code_analysis_in_separate_process => ServicesVSResources.Run_code_analysis_in_separate_process_requires_restart; - public static string Option_run_code_analysis_on_dotnet - => ServicesVSResources.Run_code_analysis_on_latest_dotnet_requires_restart; - public static string Option_analyze_source_generated_files => ServicesVSResources.Analyze_source_generated_files; @@ -150,6 +147,9 @@ public static string Option_DisplayLineSeparators public static string Option_Underline_reassigned_variables => ServicesVSResources.Underline_reassigned_variables; + public static string Option_Strike_out_obsolete_symbols + => ServicesVSResources.Strike_out_obsolete_symbols; + public static string Option_DontPutOutOrRefOnStruct => CSharpVSResources.Don_t_put_ref_or_out_on_custom_struct; @@ -261,6 +261,9 @@ public static string Option_Show_guides_for_declaration_level_constructs public static string Option_Show_guides_for_code_level_constructs => ServicesVSResources.Show_guides_for_code_level_constructs; + public static string Option_Show_guides_for_comments_and_preprocessor_regions + => ServicesVSResources.Show_guides_for_comments_and_preprocessor_regions; + public static string Option_Fading => ServicesVSResources.Fading; @@ -401,5 +404,17 @@ public static string Document_Outline public static string Option_Enable_document_outline_experimental_requires_restart => ServicesVSResources.Enable_document_outline_experimental_requires_restart; + + public static string Option_Source_Generators + => ServicesVSResources.Source_Generators; + + public static string Option_Source_generator_execution_requires_restart + => ServicesVSResources.Source_generator_execution_requires_restart; + + public static string Option_Automatic_Run_generators_after_any_change + => ServicesVSResources.Automatic_Run_generators_after_any_change; + + public static string Option_Balanced_Run_generators_after_saving_or_building + => ServicesVSResources.Balanced_Run_generators_after_saving_or_building; } } diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs index b707b2f1203c3..7c5d4766822c9 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs @@ -345,6 +345,12 @@ public string Style_PreferStaticLocalFunction set { SetXmlOption(CSharpCodeStyleOptions.PreferStaticLocalFunction, value); } } + public string Style_PreferStaticAnonymousFunction + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferStaticAnonymousFunction); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferStaticAnonymousFunction, value); } + } + public string Style_PreferSimpleUsingStatement { get { return GetXmlOption(CSharpCodeStyleOptions.PreferSimpleUsingStatement); } diff --git a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs new file mode 100644 index 0000000000000..f5fcb69f0bb5f --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.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.Composition; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Copilot; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.Internal.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Settings; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options; + +[ExportLanguageService(typeof(ICopilotOptionsService), LanguageNames.CSharp), Shared] +internal sealed class CSharpVisualStudioCopilotOptionsService : ICopilotOptionsService +{ + /// + /// Guid for UI context that is set from Copilot when the package is initialized + /// + private const string CopilotHasLoadedGuid = "871c3e1c-e58c-4ce9-b6a7-26600555739a"; + + /// + /// Guid for UI Context that is set from Copilot when sign in related UI contexts have been set properly. Used to determine when UI context status is final + /// for a set of operations. When this UI context is not active, the signed in and entitled contexts values may not be correct. + /// + private const string GitHubAccountStatusDetermined = "3049be7e-71ee-4045-a510-f8ee1a967723"; + + /// + /// Guid for UI context that is set from Copilot when we detect a GitHub account is signed in. + /// + private const string GitHubAccountStatusSignedIn = "ef3ebbb7-511d-472c-ae4b-6af1bb44f378"; + + /// + /// Guid for UI context that is set from VS Identity Service when we detect that a signed in GitHub account is entitled to access Copilot. + /// + private const string GitHubAccountStatusIsCopilotEntitled = "3DE3FA6E-91B2-46C1-9E9E-DD04975BB890"; + + private const string CopilotOptionNamePrefix = "Microsoft.VisualStudio.Conversations"; + private const string CopilotCodeAnalysisOptionName = "EnableCSharpCodeAnalysis"; + private const string CopilotRefineOptionName = "EnableCSharpRefineQuickActionSuggestion"; + private const string CopilotOnTheFlyDocsOptionName = "EnableCSharpOnTheFlyDocs"; + + private static readonly UIContext s_copilotHasLoadedUIContext = UIContext.FromUIContextGuid(new Guid(CopilotHasLoadedGuid)); + private static readonly UIContext s_gitHubAccountStatusDeterminedContext = UIContext.FromUIContextGuid(new Guid(GitHubAccountStatusDetermined)); + private static readonly UIContext s_gitHubAccountStatusIsCopilotEntitledUIContext = UIContext.FromUIContextGuid(new Guid(GitHubAccountStatusIsCopilotEntitled)); + private static readonly UIContext s_gitHubAccountStatusSignedInUIContext = UIContext.FromUIContextGuid(new Guid(GitHubAccountStatusSignedIn)); + + private readonly Task _settingsManagerTask; + + /// + /// Determines if Copilot is active and the user is signed in and entitled to use Copilot. + /// + private static bool IsGithubCopilotLoadedAndSignedIn + => s_copilotHasLoadedUIContext.IsActive + && s_gitHubAccountStatusDeterminedContext.IsActive + && s_gitHubAccountStatusSignedInUIContext.IsActive + && s_gitHubAccountStatusIsCopilotEntitledUIContext.IsActive; + + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpVisualStudioCopilotOptionsService( + IVsService settingsManagerService, + IThreadingContext threadingContext) + { + _settingsManagerTask = settingsManagerService.GetValueAsync(threadingContext.DisposalToken); + } + + public async Task IsCopilotOptionEnabledAsync(string optionName) + { + if (!IsGithubCopilotLoadedAndSignedIn) + return false; + + var settingManager = await _settingsManagerTask.ConfigureAwait(false); + // The bool setting is persisted as 0=None, 1=True, 2=False, so it needs to be retrieved as an int. + return settingManager.TryGetValue($"{CopilotOptionNamePrefix}.{optionName}", out int isEnabled) == GetValueResult.Success + && isEnabled == 1; + } + + public Task IsCodeAnalysisOptionEnabledAsync() + => IsCopilotOptionEnabledAsync(CopilotCodeAnalysisOptionName); + + public Task IsRefineOptionEnabledAsync() + => IsCopilotOptionEnabledAsync(CopilotRefineOptionName); + + public Task IsOnTheFlyDocsOptionEnabledAsync() + => IsCopilotOptionEnabledAsync(CopilotOnTheFlyDocsOptionName); +} diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs index fc8fe2951f882..7b7dfdecfe67e 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs @@ -1548,6 +1548,18 @@ int fibonacci(int n) }} "; + private static readonly string s_preferStaticAnonymousFunction = $$""" + using System; + //[ + // {{ServicesVSResources.Prefer_colon}} + Func f = static i => i * i; + //] + //[ + // {{ServicesVSResources.Over_colon}} + Func f = i => i * i; + //] + """; + #endregion #region New Line Preferences @@ -2292,6 +2304,7 @@ internal StyleViewModel(OptionStore optionStore, IServiceProvider serviceProvide CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferReadonly, ServicesVSResources.Prefer_readonly_fields, s_preferReadonly, s_preferReadonly, this, optionStore, modifierGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferReadOnlyStruct, ServicesVSResources.Prefer_read_only_struct, s_preferReadOnlyStruct, s_preferReadOnlyStruct, this, optionStore, modifierGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferStaticLocalFunction, ServicesVSResources.Prefer_static_local_functions, s_preferStaticLocalFunction, s_preferStaticLocalFunction, this, optionStore, modifierGroupTitle)); + CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferStaticAnonymousFunction, ServicesVSResources.Prefer_static_anonymous_functions, s_preferStaticAnonymousFunction, s_preferStaticAnonymousFunction, this, optionStore, modifierGroupTitle)); // Parameter preferences AddParameterOptions(optionStore, parameterPreferencesGroupTitle); diff --git a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef index ee45c4813cee4..852593e3f17a3 100644 --- a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef @@ -198,4 +198,4 @@ [$RootKey$\SettingsManifests\{13c3bbb4-f18f-4111-9f54-a0fb010d9194}] @="Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService.CSharpPackage" "ManifestPath"="$PackageFolder$\UnifiedSettings\csharpSettings.registration.json" -"CacheTag"=qword:08DC1824DFE0117B +"CacheTag"=qword:CE76341698AB8CD3 diff --git a/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs b/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs index 2aa02c6a5e89e..861723fa94b2e 100644 --- a/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs +++ b/src/VisualStudio/CSharp/Impl/Progression/CSharpProgressionLanguageService.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServices.Implementation.Progression; @@ -58,12 +59,11 @@ public IEnumerable GetTopLevelNodesFromDocument(SyntaxNode root, Can // We implement this method lazily so we are able to abort as soon as we need to. if (!cancellationToken.IsCancellationRequested) { - var nodes = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var nodes); nodes.Push(root); - while (nodes.Count > 0) + while (nodes.TryPop(out var node)) { - var node = nodes.Pop(); if (!cancellationToken.IsCancellationRequested) { if (node.Kind() is SyntaxKind.ClassDeclaration or diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/OpenSemanticSearchWindowCommand.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/OpenSemanticSearchWindowCommand.cs new file mode 100644 index 0000000000000..53096f1cefd6a --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/OpenSemanticSearchWindowCommand.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; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.Commands; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp; + +/// +/// Implements View/Other Windows/Semantic Search command. +/// +[VisualStudioContribution] +internal sealed class OpenSemanticSearchWindowCommand : Command +{ + public override CommandConfiguration CommandConfiguration => new("%CSharpLanguageServiceExtension.OpenSemanticSearchWindow.DisplayName%") + { + Icon = new(ImageMoniker.KnownValues.FindSymbol, IconSettings.IconAndText), + Placements = new[] { CommandPlacement.KnownPlacements.ViewOtherWindowsMenu.WithPriority(0x8010) }, + VisibleWhen = ActivationConstraint.UIContext(Guid.Parse(SemanticSearchFeatureFlag.UIContextId)) + }; + + public override Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) + => Extensibility.Shell().ShowToolWindowAsync(activate: true, cancellationToken); +} diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.cs new file mode 100644 index 0000000000000..1d8310925273f --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchDocumentNavigationService.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.Composition; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.SemanticSearch; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp; + +[ExportWorkspaceService(typeof(IDocumentNavigationService), WorkspaceKind.SemanticSearch), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class SemanticSearchDocumentNavigationService(SemanticSearchToolWindowImpl window) : IDocumentNavigationService +{ + public Task CanNavigateToSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) + => SpecializedTasks.True; + + public Task CanNavigateToPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) + => SpecializedTasks.False; + + public Task GetLocationForSpanAsync(Workspace workspace, DocumentId documentId, TextSpan textSpan, bool allowInvalidSpan, CancellationToken cancellationToken) + { + Debug.Assert(workspace is SemanticSearchWorkspace); + Debug.Assert(documentId == SemanticSearchUtilities.GetQueryDocumentId(workspace.CurrentSolution)); + + return Task.FromResult(window.GetNavigableLocation(textSpan)); + } + + public Task GetLocationForPositionAsync(Workspace workspace, DocumentId documentId, int position, int virtualSpace, CancellationToken cancellationToken) + => SpecializedTasks.Null(); +} diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindow.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindow.cs new file mode 100644 index 0000000000000..93895a56cfd32 --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindow.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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.ToolWindows; +using Microsoft.VisualStudio.Extensibility.VSSdkCompatibility; +using Microsoft.VisualStudio.RpcContracts.RemoteUI; +using Dock = Microsoft.VisualStudio.Extensibility.ToolWindows.Dock; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp; + +[VisualStudioContribution] +internal sealed class SemanticSearchToolWindow(MefInjection impl) : ToolWindow +{ + /// + /// HACK: Id of the tool window needed for finding tool window frame. This is created by Gladstone by hashing the full type names of + /// and types. + /// + internal static readonly Guid Id = new("91ef2fc9-e39d-1962-9b55-7047b01b40f7"); + + // Initialized by InitializeAsync + private SemanticSearchToolWindowImpl _impl = null!; + + private IRemoteUserControl? _lazyContent; + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _lazyContent?.Dispose(); + } + + base.Dispose(disposing); + } + + public override async Task InitializeAsync(CancellationToken cancellationToken) + { + await base.InitializeAsync(cancellationToken).ConfigureAwait(false); + + Title = string.Format(ServicesVSResources.Semantic_search_0, LanguageNames.CSharp); + + _impl = await impl.GetServiceAsync().ConfigureAwait(false); + } + + public override ToolWindowConfiguration ToolWindowConfiguration => new() + { + Placement = ToolWindowPlacement.Floating, + DockDirection = Dock.Bottom, + AllowAutoCreation = true, + }; + + public override async Task GetContentAsync(CancellationToken cancellationToken) + { + var content = _lazyContent; + if (content != null) + { + return content; + } + + var element = await _impl.InitializeAsync(cancellationToken).ConfigureAwait(false); + Interlocked.CompareExchange(ref _lazyContent, new WpfControlWrapper(element), null); + return _lazyContent; + } +} diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs new file mode 100644 index 0000000000000..7c4a4ccee01ee --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs @@ -0,0 +1,554 @@ +// Licensed to the .NET Foundation under one or more 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 System.Windows; +using System.Windows.Automation; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Markup; +using System.Windows.Media; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Navigation; +using Microsoft.CodeAnalysis.Notification; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.SemanticSearch; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Imaging; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Threading; +using Microsoft.VisualStudio.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp; + +using TextSpan = Microsoft.CodeAnalysis.Text.TextSpan; + +[Shared] +[Export(typeof(ISemanticSearchWorkspaceHost))] +[Export(typeof(SemanticSearchToolWindowImpl))] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class SemanticSearchToolWindowImpl( + IHostWorkspaceProvider hostWorkspaceProvider, + IThreadingContext threadingContext, + ITextEditorFactoryService textEditorFactory, + ITextDocumentFactoryService textDocumentFactory, + IContentTypeRegistryService contentTypeRegistry, + IVsEditorAdaptersFactoryService vsEditorAdaptersFactoryService, + IAsynchronousOperationListenerProvider listenerProvider, + IGlobalOptionService globalOptions, + VisualStudioWorkspace workspace, + IStreamingFindUsagesPresenter resultsPresenter, + IVsService vsUIShellProvider) : ISemanticSearchWorkspaceHost, OptionsProvider +{ + private const int ToolBarHeight = 26; + private const int ToolBarButtonSize = 20; + + private static readonly Lazy s_buttonTemplate = new(CreateButtonTemplate); + + private readonly IContentType _contentType = contentTypeRegistry.GetContentType(ContentTypeNames.CSharpContentType); + private readonly IAsynchronousOperationListener _asyncListener = listenerProvider.GetListener(FeatureAttribute.SemanticSearch); + + private readonly Lazy _semanticSearchWorkspace + = new(() => new SemanticSearchEditorWorkspace( + hostWorkspaceProvider.Workspace.Services.HostServices, + CSharpSemanticSearchUtilities.Configuration, + threadingContext, + listenerProvider)); + + // access interlocked: + private volatile CancellationTokenSource? _pendingExecutionCancellationSource; + + // Access on UI thread only: + private Button? _executeButton; + private Button? _cancelButton; + private IWpfTextView? _textView; + private ITextBuffer? _textBuffer; + + public async Task InitializeAsync(CancellationToken cancellationToken) + { + // TODO: replace with XAML once we can represent the editor as a XAML element + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1927626 + + // TODO: Add toolbar and convert Execute and Cancel buttons to commands. + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1978331 + + // The WPF control needs to be created on an UI thread + await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var vsUIShell = await vsUIShellProvider.GetValueAsync(cancellationToken).ConfigureAwait(false); + + var textViewHost = CreateTextViewHost(vsUIShell); + var textViewControl = textViewHost.HostControl; + _textView = textViewHost.TextView; + _textBuffer = textViewHost.TextView.TextBuffer; + + // enable LSP: + Contract.ThrowIfFalse(textDocumentFactory.TryGetTextDocument(_textBuffer, out var textDocument)); + textDocument.Rename(SemanticSearchUtilities.GetDocumentFilePath(LanguageNames.CSharp)); + + var toolWindowGrid = new Grid(); + toolWindowGrid.ColumnDefinitions.Add(new ColumnDefinition()); + toolWindowGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(ToolBarHeight, GridUnitType.Pixel) }); + toolWindowGrid.RowDefinitions.Add(new RowDefinition()); + + var toolbarGrid = new Grid(); + + // Set dynamically, so that it gets refreshed when theme changes: + toolbarGrid.SetResourceReference(Control.BackgroundProperty, EnvironmentColors.CommandBarGradientBrushKey); + + toolbarGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(ToolBarHeight, GridUnitType.Pixel) }); + toolbarGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(ToolBarHeight, GridUnitType.Pixel) }); + toolbarGrid.ColumnDefinitions.Add(new ColumnDefinition()); + + var executeButton = CreateButton( + KnownMonikers.Run, + automationName: CSharpVSResources.RunQuery, + acceleratorKey: "Ctrl+Enter", + toolTip: CSharpVSResources.RunQueryCommandToolTip); + + executeButton.Click += (_, _) => RunQuery(); + _executeButton = executeButton; + + var cancelButton = CreateButton( + KnownMonikers.Stop, + automationName: CSharpVSResources.CancelQuery, + acceleratorKey: "Escape", + toolTip: CSharpVSResources.CancelQueryCommandToolTip); + + cancelButton.Click += (_, _) => CancelQuery(); + cancelButton.IsEnabled = false; + _cancelButton = cancelButton; + + toolWindowGrid.Children.Add(toolbarGrid); + toolWindowGrid.Children.Add(textViewControl); + toolbarGrid.Children.Add(executeButton); + toolbarGrid.Children.Add(cancelButton); + + // placement within the tool window grid: + + Grid.SetRow(textViewControl, 1); + Grid.SetColumn(textViewControl, 0); + + Grid.SetRow(toolbarGrid, 0); + Grid.SetColumn(toolbarGrid, 0); + + // placement within the toolbar grid: + + Grid.SetRow(executeButton, 0); + Grid.SetColumn(executeButton, 0); + + Grid.SetRow(cancelButton, 0); + Grid.SetColumn(cancelButton, 1); + + await TaskScheduler.Default; + + await _semanticSearchWorkspace.Value.OpenQueryDocumentAsync(_textBuffer, cancellationToken).ConfigureAwait(false); + + return toolWindowGrid; + } + + SemanticSearchWorkspace ISemanticSearchWorkspaceHost.Workspace => _semanticSearchWorkspace.Value; + + private static Button CreateButton( + Imaging.Interop.ImageMoniker moniker, + string automationName, + string acceleratorKey, + string toolTip) + { + var image = new CrispImage() + { + Moniker = moniker, + Width = ToolBarButtonSize - 4, + Height = ToolBarButtonSize - 4, + ToolTip = toolTip, + }; + + var holder = new ContentControl + { + Height = ToolBarButtonSize, + Width = ToolBarButtonSize, + Background = Brushes.Transparent, + BorderThickness = new Thickness(0, 0, 0, 0), + }; + + ImageThemingUtilities.SetImageBackgroundColor(holder, Colors.Transparent); + holder.Content = image; + + var button = new Button() + { + Template = s_buttonTemplate.Value, + Content = holder, + }; + + button.SetValue(AutomationProperties.NameProperty, automationName); + button.SetValue(AutomationProperties.AcceleratorKeyProperty, acceleratorKey); + + image.SetBinding(CrispImage.GrayscaleProperty, new Binding(UIElement.IsEnabledProperty.Name) + { + Source = button, + Converter = new NegateBooleanConverter() + }); + + return button; + } + + private static ControlTemplate CreateButtonTemplate() + { + var context = new ParserContext(); + context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation"); + context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml"); + context.XmlnsDictionary.Add("vsui", "clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"); + + return (ControlTemplate)XamlReader.Parse($$$""" + + + + + + + + + + + + + + + + + + + + + + + + """, context); + } + + private IWpfTextViewHost CreateTextViewHost(IVsUIShell vsUIShell) + { + Contract.ThrowIfFalse(threadingContext.JoinableTaskContext.IsOnMainThread); + + var toolWindowId = SemanticSearchToolWindow.Id; + ErrorHandler.ThrowOnFailure(vsUIShell.FindToolWindow((uint)__VSFINDTOOLWIN.FTW_fFrameOnly, ref toolWindowId, out var windowFrame)); + + var commandUiGuid = VSConstants.GUID_TextEditorFactory; + ErrorHandler.ThrowOnFailure(windowFrame.SetGuidProperty((int)__VSFPROPID.VSFPROPID_InheritKeyBindings, ref commandUiGuid)); + + var roleSet = textEditorFactory.CreateTextViewRoleSet( + PredefinedTextViewRoles.Analyzable, + PredefinedTextViewRoles.Editable, + PredefinedTextViewRoles.Interactive, + PredefinedTextViewRoles.Zoomable); + + var oleServiceProvider = (OLE.Interop.IServiceProvider)Shell.Package.GetGlobalService(typeof(OLE.Interop.IServiceProvider)); + + var bufferAdapter = vsEditorAdaptersFactoryService.CreateVsTextBufferAdapter(oleServiceProvider, _contentType); + bufferAdapter.InitializeContent("", 0); + + var textViewAdapter = vsEditorAdaptersFactoryService.CreateVsTextViewAdapter(oleServiceProvider, roleSet); + + // set properties to behave like a code window: + ErrorHandler.ThrowOnFailure(((IVsTextEditorPropertyCategoryContainer)textViewAdapter).GetPropertyCategory(DefGuidList.guidEditPropCategoryViewMasterSettings, out var propContainer)); + propContainer.SetProperty(VSEDITPROPID.VSEDITPROPID_ViewComposite_AllCodeWindowDefaults, true); + propContainer.SetProperty(VSEDITPROPID.VSEDITPROPID_ViewGlobalOpt_AutoScrollCaretOnTextEntry, true); + + ErrorHandler.ThrowOnFailure(textViewAdapter.Initialize( + (IVsTextLines)bufferAdapter, + IntPtr.Zero, + (uint)TextViewInitFlags.VIF_HSCROLL | (uint)TextViewInitFlags.VIF_VSCROLL | (uint)TextViewInitFlags3.VIF_NO_HWND_SUPPORT, + [new INITVIEW { fSelectionMargin = 0, fWidgetMargin = 0, fVirtualSpace = 0, fDragDropMove = 1 }])); + + var textViewHost = vsEditorAdaptersFactoryService.GetWpfTextViewHost(textViewAdapter); + Contract.ThrowIfNull(textViewHost); + + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty((int)__VSFPROPID.VSFPROPID_ViewHelper, textViewAdapter)); + + _ = new CommandFilter(this, textViewAdapter); + + return textViewHost; + } + + private bool IsExecutingUIState() + { + Contract.ThrowIfFalse(threadingContext.JoinableTaskContext.IsOnMainThread); + Contract.ThrowIfNull(_executeButton); + + return !_executeButton.IsEnabled; + } + + private void UpdateUIState() + { + Contract.ThrowIfFalse(threadingContext.JoinableTaskContext.IsOnMainThread); + Contract.ThrowIfNull(_executeButton); + Contract.ThrowIfNull(_cancelButton); + + // reflect the actual state in UI: + var isExecuting = _pendingExecutionCancellationSource != null; + + _executeButton.IsEnabled = !isExecuting; + _cancelButton.IsEnabled = isExecuting; + } + + private void CancelQuery() + { + Contract.ThrowIfFalse(threadingContext.JoinableTaskContext.IsOnMainThread); + Contract.ThrowIfFalse(IsExecutingUIState()); + + // The query might have been cancelled already but the UI may not be updated yet: + var pendingExecutionCancellationSource = Interlocked.Exchange(ref _pendingExecutionCancellationSource, null); + pendingExecutionCancellationSource?.Cancel(); + + UpdateUIState(); + } + + private void RunQuery() + { + Contract.ThrowIfFalse(threadingContext.JoinableTaskContext.IsOnMainThread); + Contract.ThrowIfFalse(!IsExecutingUIState()); + Contract.ThrowIfNull(_textBuffer); + + var cancellationSource = new CancellationTokenSource(); + + // Cancel execution that's in progress (if any) - may occur when UI state hasn't been updated yet based on the actual state. + Interlocked.Exchange(ref _pendingExecutionCancellationSource, cancellationSource)?.Cancel(); + + UpdateUIState(); + + var (presenterContext, presenterCancellationToken) = resultsPresenter.StartSearch(ServicesVSResources.Semantic_search_results, StreamingFindUsagesPresenterOptions.Default); + presenterCancellationToken.Register(() => cancellationSource?.Cancel()); + + var querySolution = _semanticSearchWorkspace.Value.CurrentSolution; + var queryDocument = SemanticSearchUtilities.GetQueryDocument(querySolution); + + var resultsObserver = new ResultsObserver(queryDocument, presenterContext); + + var completionToken = _asyncListener.BeginAsyncOperation(nameof(SemanticSearchToolWindow) + ".Execute"); + _ = ExecuteAsync(cancellationSource.Token).ReportNonFatalErrorAsync().CompletesAsyncOperation(completionToken); + + async Task ExecuteAsync(CancellationToken cancellationToken) + { + await TaskScheduler.Default; + + ExecuteQueryResult result = default; + + var canceled = false; + string? queryString = null; + + try + { + var solution = workspace.CurrentSolution; + + if (solution.ProjectIds is []) + { + await presenterContext.ReportNoResultsAsync(ServicesVSResources.Search_found_no_results_no_csharp_or_vb_projects_opened, cancellationToken).ConfigureAwait(false); + } + else + { + var query = await queryDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + queryString = query.ToString(); + + result = await RemoteSemanticSearchServiceProxy.ExecuteQueryAsync( + solution, + LanguageNames.CSharp, + queryString, + SemanticSearchUtilities.ReferenceAssembliesDirectory, + resultsObserver, + this, + cancellationToken).ConfigureAwait(false); + } + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) + { + result = new ExecuteQueryResult(e.Message); + } + catch (OperationCanceledException) + { + result = new ExecuteQueryResult(ServicesVSResources.Search_cancelled); + canceled = true; + } + finally + { + // Notify the presenter even if the search has been cancelled. + var completionToken = _asyncListener.BeginAsyncOperation(nameof(SemanticSearchToolWindow) + ".Completion"); + _ = CompleteSearchAsync().ReportNonFatalErrorAsync().CompletesAsyncOperation(completionToken); + + // Only clear pending source if it is the same as our source (otherwise, another execution has already kicked off): + Interlocked.CompareExchange(ref _pendingExecutionCancellationSource, value: null, cancellationSource); + + // Dispose cancellation source and clear it, so that the presenterCancellationToken handler won't attempt to cancel: + var source = Interlocked.Exchange(ref cancellationSource, null); + Contract.ThrowIfNull(source); + source.Dispose(); + + await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); + + // Update UI: + UpdateUIState(); + + async Task CompleteSearchAsync() + { + var errorMessage = result.ErrorMessage; + + if (errorMessage != null) + { + if (result.ErrorMessageArgs != null) + { + errorMessage = string.Format(errorMessage, result.ErrorMessageArgs); + } + + await presenterContext.ReportMessageAsync( + errorMessage, + canceled ? NotificationSeverity.Information : NotificationSeverity.Error, + CancellationToken.None).ConfigureAwait(false); + } + + await presenterContext.OnCompletedAsync(CancellationToken.None).ConfigureAwait(false); + + if (queryString != null) + { + Logger.Log(FunctionId.SemanticSearch_QueryExecution, KeyValueLogMessage.Create(map => + { + map["Query"] = new PiiValue(queryString); + + if (canceled) + { + map["Canceled"] = true; + } + else if (result.ErrorMessage != null) + { + map["ErrorMessage"] = result.ErrorMessage; + + if (result.ErrorMessageArgs != null) + { + map["ErrorMessageArgs"] = new PiiValue(string.Join("|", result.ErrorMessageArgs)); + } + } + + map["ExecutionTimeMilliseconds"] = (long)result.ExecutionTime.TotalMilliseconds; + map["EmitTime"] = (long)result.EmitTime.TotalMilliseconds; + })); + } + } + } + } + } + + public NavigableLocation GetNavigableLocation(TextSpan textSpan) + => new(async (options, cancellationToken) => + { + await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + Contract.ThrowIfNull(_textView); + + var textSnapshot = _textView.TextBuffer.CurrentSnapshot; + var snapshotSpan = new SnapshotSpan(textSnapshot, textSpan.Start, textSpan.Length); + + _textView.Selection.Select(snapshotSpan, isReversed: false); + _textView.ViewScroller.EnsureSpanVisible(snapshotSpan, EnsureSpanVisibleOptions.AlwaysCenter); + + // Moving the caret must be the last operation involving surfaceBufferSpan because + // it might update the version number of textView.TextSnapshot (VB does line commit + // when the caret leaves a line which might cause pretty listing), which must be + // equal to surfaceBufferSpan.SnapshotSpan.Snapshot's version number. + _textView.Caret.MoveTo(snapshotSpan.Start); + + _textView.VisualElement.Focus(); + + return true; + }); + + public ValueTask GetOptionsAsync(Microsoft.CodeAnalysis.Host.LanguageServices languageServices, CancellationToken cancellationToken) + => new(globalOptions.GetClassificationOptions(languageServices.Language)); + + internal sealed class ResultsObserver(Document queryDocument, IFindUsagesContext presenterContext) : ISemanticSearchResultsObserver + { + public ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) + => presenterContext.OnDefinitionFoundAsync(definition, cancellationToken); + + public ValueTask AddItemsAsync(int itemCount, CancellationToken cancellationToken) + => presenterContext.ProgressTracker.AddItemsAsync(itemCount, cancellationToken); + + public ValueTask ItemsCompletedAsync(int itemCount, CancellationToken cancellationToken) + => presenterContext.ProgressTracker.ItemsCompletedAsync(itemCount, cancellationToken); + + public ValueTask OnUserCodeExceptionAsync(UserCodeExceptionInfo exception, CancellationToken cancellationToken) + => presenterContext.OnDefinitionFoundAsync( + new SearchExceptionDefinitionItem(exception.Message, exception.TypeName, exception.StackTrace, new DocumentSpan(queryDocument, exception.Span)), cancellationToken); + + public async ValueTask OnCompilationFailureAsync(ImmutableArray errors, CancellationToken cancellationToken) + { + foreach (var error in errors) + { + await presenterContext.OnDefinitionFoundAsync(new SearchCompilationFailureDefinitionItem(error, queryDocument), cancellationToken).ConfigureAwait(false); + } + } + } + + internal sealed class CommandFilter : IOleCommandTarget + { + private readonly SemanticSearchToolWindowImpl _window; + private readonly IOleCommandTarget _editorCommandTarget; + + public CommandFilter(SemanticSearchToolWindowImpl window, IVsTextView textView) + { + _window = window; + ErrorHandler.ThrowOnFailure(textView.AddCommandFilter(this, out _editorCommandTarget)); + } + + public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) + => _editorCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + + public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) + { + if (pguidCmdGroup == VSConstants.VSStd2K) + { + switch ((VSConstants.VSStd2KCmdID)nCmdID) + { + case VSConstants.VSStd2KCmdID.OPENLINEABOVE: + if (!_window.IsExecutingUIState()) + { + _window.RunQuery(); + return VSConstants.S_OK; + } + + break; + + case VSConstants.VSStd2KCmdID.CANCEL: + if (_window.IsExecutingUIState()) + { + _window.CancelQuery(); + return VSConstants.S_OK; + } + + break; + } + } + + return _editorCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + } + } +} diff --git a/src/VisualStudio/CSharp/Impl/Snippets/CSharpSnippetExpansionLanguageHelper.cs b/src/VisualStudio/CSharp/Impl/Snippets/CSharpSnippetExpansionLanguageHelper.cs index e4424fea36c34..27b49fa8185d1 100644 --- a/src/VisualStudio/CSharp/Impl/Snippets/CSharpSnippetExpansionLanguageHelper.cs +++ b/src/VisualStudio/CSharp/Impl/Snippets/CSharpSnippetExpansionLanguageHelper.cs @@ -27,6 +27,8 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.Snippets { + using static CSharpSyntaxTokens; + [ExportLanguageService(typeof(ISnippetExpansionLanguageHelper), LanguageNames.CSharp)] [Shared] internal class CSharpSnippetExpansionLanguageHelper : AbstractSnippetExpansionLanguageHelper @@ -140,7 +142,7 @@ private static List GetUsingDirectivesToAdd( { // Retry by parsing the namespace as a name and constructing a using directive from it candidateUsing = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(namespaceToImport)) - .WithUsingKeyword(SyntaxFactory.Token(SyntaxKind.UsingKeyword).WithTrailingTrivia(SyntaxFactory.Space)); + .WithUsingKeyword(UsingKeyword.WithTrailingTrivia(SyntaxFactory.Space)); } if (!existingUsings.Any(u => u.IsEquivalentTo(candidateUsing, topLevel: false))) diff --git a/src/VisualStudio/CSharp/Impl/Snippets/SnippetCommandHandler.cs b/src/VisualStudio/CSharp/Impl/Snippets/SnippetCommandHandler.cs index 62d64c8962b3a..1cebb71413754 100644 --- a/src/VisualStudio/CSharp/Impl/Snippets/SnippetCommandHandler.cs +++ b/src/VisualStudio/CSharp/Impl/Snippets/SnippetCommandHandler.cs @@ -34,28 +34,22 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.Snippets [Order(After = Microsoft.CodeAnalysis.Editor.PredefinedCommandHandlerNames.SignatureHelpAfterCompletion)] [Order(Before = nameof(CompleteStatementCommandHandler))] [Order(Before = Microsoft.CodeAnalysis.Editor.PredefinedCommandHandlerNames.AutomaticLineEnder)] - internal sealed class SnippetCommandHandler : - AbstractSnippetCommandHandler, + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class SnippetCommandHandler( + IThreadingContext threadingContext, + IVsEditorAdaptersFactoryService editorAdaptersFactoryService, + IVsService textManager, + EditorOptionsService editorOptionsService) : + AbstractSnippetCommandHandler(threadingContext, editorOptionsService, textManager), ICommandHandler, IChainedCommandHandler { - private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SnippetCommandHandler( - IThreadingContext threadingContext, - IVsEditorAdaptersFactoryService editorAdaptersFactoryService, - IVsService textManager, - EditorOptionsService editorOptionsService) - : base(threadingContext, editorOptionsService, textManager) - { - _editorAdaptersFactoryService = editorAdaptersFactoryService; - } + private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService = editorAdaptersFactoryService; public bool ExecuteCommand(SurroundWithCommandArgs args, CommandExecutionContext context) { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (!AreSnippetsEnabled(args)) { @@ -67,7 +61,7 @@ public bool ExecuteCommand(SurroundWithCommandArgs args, CommandExecutionContext public CommandState GetCommandState(SurroundWithCommandArgs args) { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (!AreSnippetsEnabled(args)) { @@ -90,7 +84,7 @@ public CommandState GetCommandState(TypeCharCommandArgs args, Func public void ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext) { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (args.TypedChar == ';' && AreSnippetsEnabledWithClient(args, out var snippetExpansionClient) && snippetExpansionClient.IsFullMethodCallSnippet) diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf index 827dfef8ab73a..467e1c0d5b777 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf @@ -57,6 +57,16 @@ C# Interactive + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open Při otevření souboru sbalit usings @@ -172,6 +182,21 @@ Odebrat nepotřebné direktivy using + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions Zobrazit nápovědy pro výrazy new diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf index 5fe89b000b3e4..3e008e33f0a9f 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf @@ -57,6 +57,16 @@ C# Interactive + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open Reduzieren von Usings beim Öffnen einer Datei @@ -172,6 +182,21 @@ Nicht benötigte Using-Direktiven entfernen + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions Hinweise für new-Ausdrücke anzeigen diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf index c82475b89aae7..0d2916d3c3679 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf @@ -57,6 +57,16 @@ C# interactivo + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open Contraer instrucciones Using al abrir el archivo @@ -172,6 +182,21 @@ Eliminar instrucciones Using innecesarias + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions Mostrar sugerencias para las expresiones "new" diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf index 1a90cb4f1859e..e9c9d2eb31b9d 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf @@ -57,6 +57,16 @@ C# Interactive + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open Réduire les utilisations sur le fichier ouvert @@ -172,6 +182,21 @@ Supprimer les Usings inutiles + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions Afficher les indicateurs pour les expressions 'new' diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf index 16058cee02eda..4c0d2484c40ee 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf @@ -57,6 +57,16 @@ C# Interactive + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open Comprimi gli utilizzi all'apertura del file @@ -172,6 +182,21 @@ Rimuovi istruzioni using non necessarie + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions Mostra suggerimenti per le espressioni 'new' diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf index 2c1db671696b5..8002973babd12 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf @@ -57,6 +57,16 @@ C# インタラクティブ + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open ファイルを開くときに usings を折りたたむ @@ -172,6 +182,21 @@ 不要な using の削除 + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions 'new' 式のヒントを表示する diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf index 361bd1d08c30a..71aeb34a7c9af 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf @@ -57,6 +57,16 @@ C# 대화형 + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open 파일을 열 때 사용 축소 @@ -172,6 +182,21 @@ 불필요한 Using 제거 + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions 'new' 식에 대한 힌트 표시 diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf index 369bfaf224361..1084bed52c2b6 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf @@ -57,6 +57,16 @@ C# Interactive + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open Zwiń użycia przy otwieraniu pliku @@ -172,6 +182,21 @@ Usuń niepotrzebne użycia + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions Pokaż wskazówki dla wyrażeń „new” diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf index fb04081eda7bd..9f5fa6e7e9397 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf @@ -57,6 +57,16 @@ C# Interativo + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open Recolher os usos no arquivo aberto @@ -172,6 +182,21 @@ Remover Usos Desnecessários + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions Mostrar as dicas para as expressões 'new' diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf index 5bb3ba19a965e..7b8279944f595 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf @@ -57,6 +57,16 @@ C# Interactive + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open Сворачивать директивы using при открытии файлов @@ -172,6 +182,21 @@ Удалить ненужные директивы using + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions Отображать подсказки для выражений "new" diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf index bbbf0a9425801..42bfac7a176f6 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf @@ -57,6 +57,16 @@ C# Etkileşimli + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open Dosya açıkken using'leri daralt @@ -172,6 +182,21 @@ Gereksiz Kullanımları Kaldır + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions 'new' ifadeleri için ipuçlarını göster diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf index 87692c2ad2c7b..7a228e88380fd 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf @@ -57,6 +57,16 @@ C# 交互窗口 + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open 打开文件时折叠 using @@ -172,6 +182,21 @@ 删除不必要的 Using + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions 显示 "new" 表达式的提示 diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf index 1ad6b54c70796..b1c21d494c2b7 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf @@ -57,6 +57,16 @@ C# 互動 + + Cancel Query + Cancel Query + + + + Cancel (Escape) + Cancel (Escape) + + Collapse usings on file open 在檔案開啟時折疊使用 @@ -172,6 +182,21 @@ 移除不必要的 Using + + Run Query + Run Query + + + + Run (Ctrl+Enter) + Run (Ctrl+Enter) + + + + Semantic Search + Semantic Search + + Show hints for 'new' expressions 顯示 'new' 運算式的提示 diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf index e6be2e1fc297a..2b76105cd8097 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.cs.xlf @@ -412,37 +412,37 @@ Zobrazit položky z neimportovaných oborů názvů (experimentální); Always add new line on enter - Always add new line on enter + Při stisku Enter vždy přidat nový řádek Always include snippets - Always include snippets + Vždy zahrnovat fragmenty Default - Default + Výchozí Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + Zahrnout fragmenty kódu při zadání tabulátoru za identifikátor Never add new line on enter - Never add new line on enter + Při stisku Enter nikdy nepřidávat nový řádek Never include snippets - Never include snippets + Nikdy nezahrnovat fragmenty Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + Při stisku Enter přidat nový řádek jenom po dopsání celého slova diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf index 29b1b9ac5c565..efc5cd572f7fe 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.de.xlf @@ -412,37 +412,37 @@ Elemente aus nicht importierten Namespaces anzeigen (experimentell); Always add new line on enter - Always add new line on enter + Nach Drücken der EINGABETASTE immer neue Zeile hinzufügen Always include snippets - Always include snippets + Schnipsel immer einschließen Default - Default + Standard Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + Ausschnitte einschließen, wenn Tab nach einem Bezeichner eingegeben wird Never add new line on enter - Never add new line on enter + Nach Drücken der EINGABETASTE niemals neue Zeile hinzufügen Never include snippets - Never include snippets + Schnipsel nie einschließen Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + Nach Drücken der EINGABETASTE nur nach dem Ende eines vollständigen Worts neue Zeile hinzufügen diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf index 7b5662fa39c84..6d46f239f5085 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.es.xlf @@ -412,37 +412,37 @@ Mostrar elementos de espacios de nombres no importados (experimental); Always add new line on enter - Always add new line on enter + Siempre agregar una nueva línea al pulsar Intro Always include snippets - Always include snippets + Incluir siempre fragmentos de código Default - Default + Predeterminado Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + Incluir fragmentos de código cuando Tab se escriba después de un identificador Never add new line on enter - Never add new line on enter + Nunca agregar una nueva línea al pulsar Intro Never include snippets - Never include snippets + No incluir nunca fragmentos de código Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + Solo agregar una nueva línea al pulsar Intro cuando se haya terminado de escribir completamente una palabra diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf index 32677cef8a0a9..a4e6bb816ef44 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.fr.xlf @@ -412,37 +412,37 @@ Afficher les éléments des espaces de noms qui ne sont pas importés (expérime Always add new line on enter - Always add new line on enter + Toujours ajouter une nouvelle ligne en appuyant sur Entrée Always include snippets - Always include snippets + Toujours inclure les extraits de code Default - Default + Par défaut Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + Inclure des extraits de code quand Tab est typé après un identificateur Never add new line on enter - Never add new line on enter + Ne jamais ajouter de nouvelle ligne en appuyant sur Entrée Never include snippets - Never include snippets + Ne jamais inclure d'extrait de code Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + Ajouter une nouvelle ligne en appuyant sur Entrée seulement après la fin d'un mot entièrement tapé diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf index f0dfec70e1585..a2043ee6d5bea 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.it.xlf @@ -412,37 +412,37 @@ Mostra elementi da spazi dei nomi non importati (sperimentale); Always add new line on enter - Always add new line on enter + Aggiungi sempre una nuova riga dopo INVIO Always include snippets - Always include snippets + Includi sempre i frammenti Default - Default + Predefinito Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + Includi i frammenti quando si digita Tab dopo un identificatore Never add new line on enter - Never add new line on enter + Non aggiungere mai una nuova riga dopo INVIO Never include snippets - Never include snippets + Non includere mai i frammenti Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + Aggiungi una nuova riga dopo INVIO solo alla fine della parola digitata diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf index 5b7872afd6bb2..5fd4f43e9b47d 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ja.xlf @@ -412,37 +412,37 @@ Enter キーで常に新しい行を追加する; Always add new line on enter - Always add new line on enter + Enter を押すと常に新しい行を追加します Always include snippets - Always include snippets + 常にスニペットを含める Default - Default + 既定 Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + 識別子の後に Tab を入力したときにスニペットを含める Never add new line on enter - Never add new line on enter + Enter 時に新しい行を追加しません Never include snippets - Never include snippets + スニペットを含めない Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + 単語を完全に入力した後 Enter キーで新しい行のみを追加する diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf index b4a9d605134dc..de61a5007642b 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ko.xlf @@ -412,37 +412,37 @@ Show items from unimported namespaces (experimental); Always add new line on enter - Always add new line on enter + 입력 시 새 줄 항상 추가 Always include snippets - Always include snippets + 코드 조각 항상 포함 Default - Default + 기본값 Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + 식별자 뒤에 Tab을 입력하면 코드 조각 포함 Never add new line on enter - Never add new line on enter + 입력 시 새 줄 추가 안 함 Never include snippets - Never include snippets + 코드 조각 포함 안 함 Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + 단어를 모두 입력하고 <Enter> 키를 누르면 새 줄 추가 diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf index 26706c26c5d64..101df8c706b3c 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pl.xlf @@ -411,37 +411,37 @@ Pokaż elementy z niezaimportowanych przestrzeni nazw (funkcja eksperymentalna); Always add new line on enter - Always add new line on enter + Zawsze dodawaj nowy wiersz po naciśnięciu klawisza Enter Always include snippets - Always include snippets + Zawsze dołączaj fragmenty kodu Default - Default + Domyślne Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + Dołącz fragmenty po naciśnięciu klawisza Tab po identyfikatorze Never add new line on enter - Never add new line on enter + Nigdy nie dodawaj nowego wiersza po naciśnięciu klawisza Enter Never include snippets - Never include snippets + Nigdy nie dołączaj fragmentów kodu Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + Dodaj nowy wiersz tylko po naciśnięciu klawisza Enter na końcu w pełni wpisanego wyrazu diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf index 1297caa9e09b0..9019773350a57 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.pt-BR.xlf @@ -412,37 +412,37 @@ Mostrar itens de namespaces não importados (experimental); Always add new line on enter - Always add new line on enter + Sempre adicionar uma nova linha ao pressionar enter Always include snippets - Always include snippets + Sempre incluir snippets Default - Default + Padrão Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + Incluir snippets de código quando Tab for digitado após um identificador Never add new line on enter - Never add new line on enter + Nunca adicionar uma nova linha ao entrar Never include snippets - Never include snippets + Nunca incluir snippets Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + Somente adiciona uma nova linha após digitar toda palavra diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf index e48365faa2edb..d6320c3046efb 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.ru.xlf @@ -412,37 +412,37 @@ Show items from unimported namespaces (experimental); Always add new line on enter - Always add new line on enter + Всегда добавлять новую строку при нажатии клавиши ВВОД Always include snippets - Always include snippets + Всегда включать фрагменты кода Default - Default + По умолчанию Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + Включать фрагменты кода, когда после идентификатора нажата клавиша TAB Never add new line on enter - Never add new line on enter + Никогда не добавлять новую строку при нажатии клавиши ВВОД Never include snippets - Never include snippets + Никогда не включать фрагменты кода Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + Добавлять только новую строку при нажатии клавиши ВВОД после полностью введенного слова diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf index 4f2b330a697b9..15c065e8b8367 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.tr.xlf @@ -412,37 +412,37 @@ Ad önerilerini göster; Always add new line on enter - Always add new line on enter + Enter'a basıldığında her zaman yeni satır ekle Always include snippets - Always include snippets + Kod parçacıklarını her zaman dahil et Default - Default + Varsayılan Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + Bir tanımlayıcıdan sonra Tab yazılırsa kod parçacıklarını dahil et Never add new line on enter - Never add new line on enter + Enter'a basıldığında hiçbir zaman yeni satır ekleme Never include snippets - Never include snippets + Kod parçacıklarını hiçbir zaman dahil etme Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + Yalnızca tam olarak yazılmış bir kelimeden sonra Enter'a basıldığında yeni satır ekle diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf index 09fdd1b28d195..4af82c645ea0f 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hans.xlf @@ -412,37 +412,37 @@ Show items from unimported namespaces (experimental); Always add new line on enter - Always add new line on enter + 始终在点击回车时时添加新行 Always include snippets - Always include snippets + 始终包含片段 Default - Default + 默认值 Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + 在标识符后键入 Tab 时包含片段 Never add new line on enter - Never add new line on enter + 永远不要在点击回车后添加新行 Never include snippets - Never include snippets + 从不包含片段 Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + 只在键入完整的单词后点击回车后才添加新行 diff --git a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf index 2530541f2ab40..38cdaa0163750 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/VSPackage.zh-Hant.xlf @@ -412,37 +412,37 @@ Enter 鍵行為; Always add new line on enter - Always add new line on enter + 一律在按 ENTER 時新增新行 Always include snippets - Always include snippets + 一律包含程式碼片段 Default - Default + 預設 Include snippets when Tab is typed after an identifier - Include snippets when Tab is typed after an identifier + 在識別碼後輸入 Tab 時包含程式碼片段 Never add new line on enter - Never add new line on enter + 一律不在按 ENTER 時新增新行 Never include snippets - Never include snippets + 一律不包含程式碼片段 Only add new line on enter after end of fully typed word - Only add new line on enter after end of fully typed word + 只在完整鍵入字的結尾處按 ENTER 來新增新行 diff --git a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTests.cs b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTests.cs index c412b53e936d2..38cfd356f0961 100644 --- a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTests.cs +++ b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTests.cs @@ -80,15 +80,15 @@ static ImmutableArray SortDocumentSymbols( ImmutableArray documentSymbolData, SortOption sortOption) { - using var _ = ArrayBuilder.GetInstance(out var sortedDocumentSymbols); documentSymbolData = Sort(documentSymbolData, sortOption); + var sortedDocumentSymbols = new FixedSizeArrayBuilder(documentSymbolData.Length); foreach (var documentSymbol in documentSymbolData) { var sortedChildren = SortDocumentSymbols(documentSymbol.Children, sortOption); sortedDocumentSymbols.Add(ReplaceChildren(documentSymbol, sortedChildren)); } - return sortedDocumentSymbols.ToImmutable(); + return sortedDocumentSymbols.MoveToImmutable(); } static ImmutableArray Sort(ImmutableArray items, SortOption sortOption) 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 9814d14aa5285..b42bdacc847c3 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -80,4 +80,8 @@ + + + + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs index fb895470cd613..4364077c46c92 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs @@ -85,12 +85,6 @@ protected AbstractPersistentStorageTests() ThreadPool.SetMinThreads(Math.Max(workerThreads, NumThreads), completionPortThreads); } - internal abstract AbstractPersistentStorageService GetStorageService( - IMefHostExportProvider exportProvider, - IPersistentStorageConfiguration configuration, - IPersistentStorageFaultInjector? faultInjector, - string rootFolder); - public void Dispose() { // This should cause the service to release the cached connection it maintains for the primary workspace @@ -121,7 +115,7 @@ public async Task TestNullFilePaths() var streamName = "stream"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); var project = solution.Projects.First(); var document = project.Documents.First(); Assert.False(await storage.WriteStreamAsync(project, streamName, EncodeString(""))); @@ -142,14 +136,14 @@ public async Task CacheDirectoryInPathWithSingleQuote(Size size, bool withChecks var streamName1 = "PersistentService_Solution_WriteReadDifferentInstances1"; var streamName2 = "PersistentService_Solution_WriteReadDifferentInstances2"; - await using (var storage = await GetStorageAsync(solution, folder)) { + var storage = await GetStorageAsync(solution, folder); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); } - await using (var storage = await GetStorageAsync(solution, folder)) { + var storage = await GetStorageAsync(solution, folder); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)))); Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum)))); } @@ -163,14 +157,14 @@ public async Task PersistentService_Solution_WriteReadDifferentInstances(Size si var streamName1 = "PersistentService_Solution_WriteReadDifferentInstances1"; var streamName2 = "PersistentService_Solution_WriteReadDifferentInstances2"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)))); Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum)))); } @@ -184,16 +178,16 @@ public async Task PersistentService_Solution_WriteReadReopenSolution(Size size, var streamName1 = "PersistentService_Solution_WriteReadReopenSolution1"; var streamName2 = "PersistentService_Solution_WriteReadReopenSolution2"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); } solution = CreateOrOpenSolution(); - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)))); Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum)))); } @@ -207,7 +201,7 @@ public async Task PersistentService_Solution_WriteReadSameInstance(Size size, bo var streamName1 = "PersistentService_Solution_WriteReadSameInstance1"; var streamName2 = "PersistentService_Solution_WriteReadSameInstance2"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); @@ -223,7 +217,7 @@ public async Task PersistentService_Project_WriteReadSameInstance(Size size, boo var streamName1 = "PersistentService_Project_WriteReadSameInstance1"; var streamName2 = "PersistentService_Project_WriteReadSameInstance2"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); var project = solution.Projects.Single(); Assert.True(await storage.WriteStreamAsync(project, streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); @@ -241,7 +235,7 @@ public async Task PersistentService_Document_WriteReadSameInstance(Size size, bo var streamName1 = "PersistentService_Document_WriteReadSameInstance1"; var streamName2 = "PersistentService_Document_WriteReadSameInstance2"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); var document = solution.Projects.Single().Documents.Single(); Assert.True(await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); @@ -259,7 +253,7 @@ public async Task PersistentService_Solution_SimultaneousWrites([CombinatorialRa var streamName1 = "PersistentService_Solution_SimultaneousWrites1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); DoSimultaneousWrites(s => storage.WriteStreamAsync(streamName1, EncodeString(s))); var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(streamName1))); Assert.True(value >= 0); @@ -274,7 +268,7 @@ public async Task PersistentService_Project_SimultaneousWrites([CombinatorialRan var streamName1 = "PersistentService_Project_SimultaneousWrites1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); DoSimultaneousWrites(s => storage.WriteStreamAsync(solution.Projects.Single(), streamName1, EncodeString(s))); var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single(), streamName1))); Assert.True(value >= 0); @@ -289,7 +283,7 @@ public async Task PersistentService_Document_SimultaneousWrites([CombinatorialRa var streamName1 = "PersistentService_Document_SimultaneousWrites1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); DoSimultaneousWrites(s => storage.WriteStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, EncodeString(s))); var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single().Documents.Single(), streamName1))); Assert.True(value >= 0); @@ -303,7 +297,7 @@ public async Task PersistentService_Solution_SimultaneousReads(Size size, bool w var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_Solution_SimultaneousReads1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum))), GetData1(size)); } @@ -315,7 +309,7 @@ public async Task PersistentService_Project_SimultaneousReads(Size size, bool wi var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_Project_SimultaneousReads1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(solution.Projects.Single(), streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single(), streamName1, GetChecksum1(withChecksum))), GetData1(size)); } @@ -328,7 +322,7 @@ public async Task PersistentService_Document_SimultaneousReads(Size size, bool w var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_Document_SimultaneousReads1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, GetChecksum1(withChecksum))), GetData1(size)); } @@ -341,7 +335,7 @@ public async Task TestReadChecksumReturnsNullWhenNeverWritten([CombinatorialRang var streamName1 = "TestReadChecksumReturnsNullWhenNeverWritten"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } @@ -353,13 +347,13 @@ public async Task TestCanReadWithNullChecksumSomethingWrittenWithNonNullChecksum var streamName1 = "TestCanReadWithNullChecksumSomethingWrittenWithNonNullChecksum"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), s_checksum1)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, checksum: null))); } } @@ -372,13 +366,13 @@ public async Task TestCannotReadWithMismatchedChecksums(Size size, [Combinatoria var streamName1 = "TestCannotReadWithMismatchedChecksums"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), s_checksum1)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.Null(await storage.ReadStreamAsync(streamName1, s_checksum2)); } } @@ -391,13 +385,13 @@ public async Task TestCannotReadChecksumIfWriteDidNotIncludeChecksum(Size size, var streamName1 = "TestCannotReadChecksumIfWriteDidNotIncludeChecksum"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: null)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } } @@ -410,13 +404,13 @@ public async Task TestReadChecksumProducesWrittenChecksum(Size size, [Combinator var streamName1 = "TestReadChecksumProducesWrittenChecksum"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } } @@ -429,14 +423,14 @@ public async Task TestReadChecksumProducesLastWrittenChecksum1(Size size, [Combi var streamName1 = "TestReadChecksumProducesLastWrittenChecksum1"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1)); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: null)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } } @@ -449,14 +443,14 @@ public async Task TestReadChecksumProducesLastWrittenChecksum2(Size size, [Combi var streamName1 = "TestReadChecksumProducesLastWrittenChecksum2"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: null)); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } } @@ -469,14 +463,14 @@ public async Task TestReadChecksumProducesLastWrittenChecksum3(Size size, [Combi var streamName1 = "TestReadChecksumProducesLastWrittenChecksum3"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1)); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum2)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum2)); } } @@ -490,13 +484,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKey(Size size, [Combina var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); } @@ -511,13 +505,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocument(Size size, [Combinator var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); } @@ -532,13 +526,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKey(Size size, [Combinator var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); } @@ -553,13 +547,13 @@ public async Task TestOpenWithSolutionReadWithDocument(Size size, [Combinatorial var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); } @@ -574,13 +568,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument1(Size size, var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -598,13 +592,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument2(Size size, var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -622,13 +616,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument1(Size si var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -646,13 +640,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument2(Size si var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -670,13 +664,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKey_WriteWithSolutionKe var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); } @@ -691,13 +685,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocument_WriteWithSolutionKey(S var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); } @@ -712,13 +706,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKey_WriteWithSolutionKey(S var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); } @@ -733,13 +727,13 @@ public async Task TestOpenWithSolutionReadWithDocument_WriteWithSolutionKey(Size var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); } @@ -754,13 +748,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument1_WriteWithS var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -778,13 +772,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument2_WriteWithS var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -802,13 +796,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument1_WriteWi var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -826,13 +820,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument2_WriteWi var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -859,13 +853,13 @@ public async Task PersistentService_ReadByteTwice(Size size, bool withChecksum, var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_ReadByteTwice"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); using var stream = await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)); Contract.ThrowIfNull(stream); stream.ReadByte(); @@ -883,8 +877,8 @@ public async Task TestPersistSyntaxTreeIndex([CombinatorialRange(0, Iterations)] var document = solution.GetRequiredDocument(id); - await using (var storage = await GetStorageAsync(solution)) { + _ = await GetStorageAsync(solution); var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, default); await index.SaveAsync(document, _storageService!); @@ -903,8 +897,8 @@ public async Task TestPersistTopLevelSyntaxTreeIndex([CombinatorialRange(0, Iter var document = solution.GetRequiredDocument(id); - await using (var storage = await GetStorageAsync(solution)) { + _ = await GetStorageAsync(solution); var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, default); await index.SaveAsync(document, _storageService!); @@ -981,6 +975,7 @@ private static void DoSimultaneousWrites(Func write) protected Solution CreateOrOpenSolution(TempDirectory? persistentFolder = null, bool nullPaths = false) { persistentFolder ??= _persistentFolder; + _storageService?.GetTestAccessor().Shutdown(); var solutionFile = persistentFolder.CreateOrOpenFile("Solution1.sln").WriteAllText(""); var info = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(), solutionFile.Path); @@ -1016,13 +1011,14 @@ internal async Task GetStorageAsync( _storageService?.GetTestAccessor().Shutdown(); var configuration = new MockPersistentStorageConfiguration(solution.Id, persistentFolder.Path, throwOnFailure); - _storageService = GetStorageService(solution.Workspace.Services.SolutionServices.ExportProvider, configuration, faultInjector, _persistentFolder.Path); - var storage = await _storageService.GetStorageAsync(SolutionKey.ToSolutionKey(solution), CancellationToken.None); + _storageService = (AbstractPersistentStorageService)solution.Workspace.Services.SolutionServices.GetPersistentStorageService(); + var storage = await _storageService.GetStorageAsync( + SolutionKey.ToSolutionKey(solution), configuration, faultInjector, CancellationToken.None); // If we're injecting faults, we expect things to be strange if (faultInjector == null) { - Assert.NotEqual(NoOpPersistentStorage.TestAccessor.StorageInstance, storage); + Assert.NotEqual(NoOpPersistentStorage.TestAccessor.GetStorageInstance(SolutionKey.ToSolutionKey(solution)), storage); } return storage; @@ -1031,17 +1027,16 @@ internal async Task GetStorageAsync( internal async Task GetStorageFromKeyAsync( HostWorkspaceServices services, SolutionKey solutionKey, IPersistentStorageFaultInjector? faultInjector = null) { - // If we handed out one for a previous test, we need to shut that down first - _storageService?.GetTestAccessor().Shutdown(); var configuration = new MockPersistentStorageConfiguration(solutionKey.Id, _persistentFolder.Path, throwOnFailure: true); - _storageService = GetStorageService(services.SolutionServices.ExportProvider, configuration, faultInjector, _persistentFolder.Path); - var storage = await _storageService.GetStorageAsync(solutionKey, CancellationToken.None); + _storageService = (AbstractPersistentStorageService)services.SolutionServices.GetPersistentStorageService(); + var storage = await _storageService.GetStorageAsync( + solutionKey, configuration, faultInjector, CancellationToken.None); // If we're injecting faults, we expect things to be strange if (faultInjector == null) { - Assert.NotEqual(NoOpPersistentStorage.TestAccessor.StorageInstance, storage); + Assert.NotEqual(NoOpPersistentStorage.TestAccessor.GetStorageInstance(solutionKey), storage); } return storage; diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs index ef6a87a205418..6f1ef7522c274 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SQLite.v2; using Microsoft.CodeAnalysis.Storage; @@ -23,13 +22,6 @@ namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices /// public class SQLiteV2PersistentStorageTests : AbstractPersistentStorageTests { - internal override AbstractPersistentStorageService GetStorageService(IMefHostExportProvider exportProvider, IPersistentStorageConfiguration configuration, IPersistentStorageFaultInjector? faultInjector, string relativePathBase) - => new SQLitePersistentStorageService( - exportProvider.GetExports().Single().Value, - configuration, - exportProvider.GetExports().Single().Value.GetListener(FeatureAttribute.PersistentStorage), - faultInjector); - [Fact] public async Task TestCrashInNewConnection() { @@ -46,7 +38,7 @@ public async Task TestCrashInNewConnection() // Because instantiating the connection will fail, we will not get back // a working persistent storage. We are testing a fault recovery code path. - await using (var storage = await GetStorageAsync(solution, faultInjector: faultInjector, throwOnFailure: false)) + var storage = await GetStorageAsync(solution, faultInjector: faultInjector, throwOnFailure: false); using (var memStream = new MemoryStream()) using (var streamWriter = new StreamWriter(memStream)) { diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs new file mode 100644 index 0000000000000..cd9d0faf9bdc3 --- /dev/null +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more 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 System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Editor.CSharp.CompleteStatement; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.LanguageServices; +using Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Roslyn.VisualStudio.CSharp.UnitTests.UnifiedSettings +{ + public class CSharpUnifiedSettingsTests : UnifiedSettingsTests + { + internal override ImmutableArray OnboardedOptions => ImmutableArray.Create( + CompletionOptionsStorage.TriggerOnTypingLetters, + CompletionOptionsStorage.TriggerOnDeletion, + CompletionOptionsStorage.TriggerInArgumentLists, + CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems, + CompletionViewOptionsStorage.ShowCompletionItemFilters, + CompleteStatementOptionsStorage.AutomaticallyCompleteStatementOnSemicolon, + CompletionOptionsStorage.SnippetsBehavior, + CompletionOptionsStorage.EnterKeyBehavior, + CompletionOptionsStorage.ShowNameSuggestions, + CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, + CompletionViewOptionsStorage.EnableArgumentCompletionSnippets, + CompletionOptionsStorage.ShowNewSnippetExperienceUserOption + ); + + internal override object[] GetEnumOptionValues(IOption2 option) + { + var allValues = Enum.GetValues(option.Type).Cast(); + if (option == CompletionOptionsStorage.SnippetsBehavior) + { + // SnippetsRule.Default is used as a stub value, overridden per language at runtime. + // It is not shown in the option page + return allValues.Where(value => !value.Equals(SnippetsRule.Default)).ToArray(); + } + else if (option == CompletionOptionsStorage.EnterKeyBehavior) + { + // EnterKeyRule.Default is used as a stub value, overridden per language at runtime. + // It is not shown in the option page + return allValues.Where(value => !value.Equals(EnterKeyRule.Default)).ToArray(); + } + + return base.GetEnumOptionValues(option); + } + + internal override object GetOptionsDefaultValue(IOption2 option) + { + // The default values of some options are set at runtime. option.defaultValue is just a dummy value in this case. + // However, in unified settings we always set the correct value in registration.json. + if (option == CompletionOptionsStorage.SnippetsBehavior) + { + // CompletionOptionsStorage.SnippetsBehavior's default value is SnippetsRule.Default. + // It's overridden differently per-language at runtime. + return SnippetsRule.AlwaysInclude; + } + else if (option == CompletionOptionsStorage.EnterKeyBehavior) + { + // CompletionOptionsStorage.EnterKeyBehavior's default value is EnterKeyBehavior.Default. + // It's overridden differently per-language at runtime. + return EnterKeyRule.Never; + } + else if (option == CompletionOptionsStorage.TriggerOnDeletion) + { + // CompletionOptionsStorage.TriggerOnDeletion's default value is null. + // It's disabled by default for C# + return false; + } + else if (option == CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces) + { + // CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces's default value is null + // It's enabled by default for C# + return true; + } + else if (option == CompletionViewOptionsStorage.EnableArgumentCompletionSnippets) + { + // CompletionViewOptionsStorage.EnableArgumentCompletionSnippets' default value is null + // It's disabled by default for C# + return false; + } + else if (option == CompletionOptionsStorage.ShowNewSnippetExperienceUserOption) + { + // CompletionOptionsStorage.ShowNewSnippetExperienceUserOption's default value is null. + // It's in experiment, so disabled by default. + return false; + } + + return base.GetOptionsDefaultValue(option); + } + + [Fact] + public async Task IntelliSensePageTests() + { + using var registrationFileStream = typeof(CSharpUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("Roslyn.VisualStudio.CSharp.UnitTests.csharpSettings.registration.json"); + using var reader = new StreamReader(registrationFileStream); + var registrationFile = await reader.ReadToEndAsync().ConfigureAwait(false); + var registrationJsonObject = JObject.Parse(registrationFile, new JsonLoadSettings() { CommentHandling = CommentHandling.Ignore }); + var categoriesTitle = registrationJsonObject.SelectToken($"$.categories['textEditor.csharp'].title")!; + Assert.Equal("C#", actual: categoriesTitle.ToString()); + var optionPageId = registrationJsonObject.SelectToken("$.categories['textEditor.csharp.intellisense'].legacyOptionPageId"); + Assert.Equal(Guids.CSharpOptionPageIntelliSenseIdString, optionPageId!.ToString()); + using var pkgdefFileStream = typeof(CSharpUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("Roslyn.VisualStudio.CSharp.UnitTests.PackageRegistration.pkgdef"); + using var pkgdefReader = new StreamReader(pkgdefFileStream); + var pkgdefFile = await pkgdefReader.ReadToEndAsync().ConfigureAwait(false); + TestUnifiedSettingsCategory(registrationJsonObject, categoryBasePath: "textEditor.csharp.intellisense", languageName: LanguageNames.CSharp, pkgdefFile); + } + } +} diff --git a/src/VisualStudio/Core/Def/AnalyzerDependency/AnalyzerFileWatcherService.cs b/src/VisualStudio/Core/Def/AnalyzerDependency/AnalyzerFileWatcherService.cs index 1aafbe3f230aa..570fe959cf266 100644 --- a/src/VisualStudio/Core/Def/AnalyzerDependency/AnalyzerFileWatcherService.cs +++ b/src/VisualStudio/Core/Def/AnalyzerDependency/AnalyzerFileWatcherService.cs @@ -6,27 +6,19 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; -using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation; [Export(typeof(AnalyzerFileWatcherService))] -internal sealed class AnalyzerFileWatcherService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class AnalyzerFileWatcherService(SVsServiceProvider serviceProvider) { - private static readonly object s_analyzerChangedErrorId = new(); - - private readonly VisualStudioWorkspaceImpl _workspace; - private readonly HostDiagnosticUpdateSource _updateSource; - private readonly IVsFileChangeEx _fileChangeService; + private readonly IVsFileChangeEx _fileChangeService = (IVsFileChangeEx)serviceProvider.GetService(typeof(SVsFileChangeEx)); private readonly Dictionary _fileChangeTrackers = new(StringComparer.OrdinalIgnoreCase); @@ -38,37 +30,6 @@ internal sealed class AnalyzerFileWatcherService private readonly object _guard = new(); - private readonly DiagnosticDescriptor _analyzerChangedRule = new( - id: IDEDiagnosticIds.AnalyzerChangedId, - title: ServicesVSResources.AnalyzerChangedOnDisk, - messageFormat: ServicesVSResources.The_analyzer_assembly_0_has_changed_Diagnostics_may_be_incorrect_until_Visual_Studio_is_restarted, - category: FeaturesResources.Roslyn_HostError, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public AnalyzerFileWatcherService( - VisualStudioWorkspaceImpl workspace, - HostDiagnosticUpdateSource hostDiagnosticUpdateSource, - SVsServiceProvider serviceProvider) - { - _workspace = workspace; - _updateSource = hostDiagnosticUpdateSource; - _fileChangeService = (IVsFileChangeEx)serviceProvider.GetService(typeof(SVsFileChangeEx)); - } - - private void AddAnalyzerChangedWarningArgs(ref TemporaryArray builder, ProjectId projectId, string analyzerPath) - { - var messageArguments = new string[] { analyzerPath }; - - var project = _workspace.CurrentSolution.GetProject(projectId); - if (project != null && DiagnosticData.TryCreate(_analyzerChangedRule, messageArguments, project, out var diagnostic)) - { - _updateSource.UpdateAndAddDiagnosticsArgsForProject(ref builder, projectId, Tuple.Create(s_analyzerChangedErrorId, analyzerPath), SpecializedCollections.SingletonEnumerable(diagnostic)); - } - } - private static DateTime? GetLastUpdateTimeUtc(string fullPath) { try @@ -88,7 +49,7 @@ private void AddAnalyzerChangedWarningArgs(ref TemporaryArray.Empty; - AddAnalyzerChangedWarningArgs(ref argsBuilder.AsRef(), projectId, filePath); - _updateSource.RaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - } - // If the the tracker is in place, at this point we can stop checking any further for this assembly if (tracker.PreviousCallToStartFileChangeHasAsynchronouslyCompleted) { @@ -152,20 +106,5 @@ private void Tracker_UpdatedOnDisk(object sender, EventArgs e) tracker.Dispose(); tracker.UpdatedOnDisk -= Tracker_UpdatedOnDisk; - - // Traverse the chain of requesting assemblies to get back to the original analyzer - // assembly. - using var argsBuilder = TemporaryArray.Empty; - foreach (var project in _workspace.CurrentSolution.Projects) - { - var analyzerFileReferences = project.AnalyzerReferences.OfType(); - - if (analyzerFileReferences.Any(a => a.FullPath.Equals(filePath, StringComparison.OrdinalIgnoreCase))) - { - AddAnalyzerChangedWarningArgs(ref argsBuilder.AsRef(), project.Id, filePath); - } - } - - _updateSource.RaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); } } diff --git a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs index d9887f9c4ff35..0be27dc93912f 100644 --- a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs +++ b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs @@ -144,7 +144,7 @@ SymbolKind.Event or if (symbol.Kind == SymbolKind.Field) { - return SpecializedCollections.SingletonEnumerable(new FieldReferenceFinder(symbol, project.Id, AsyncListener, this)); + return [new FieldReferenceFinder(symbol, project.Id, AsyncListener, this)]; } return null; diff --git a/src/VisualStudio/Core/Def/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs b/src/VisualStudio/Core/Def/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs index 2ced2cf36a170..337366293ca3e 100644 --- a/src/VisualStudio/Core/Def/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs +++ b/src/VisualStudio/Core/Def/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ChangeSignature; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.Text.Classification; @@ -13,21 +14,16 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature; [ExportWorkspaceService(typeof(IChangeSignatureOptionsService), ServiceLayer.Host), Shared] -internal class VisualStudioChangeSignatureOptionsService : ForegroundThreadAffinitizedObject, IChangeSignatureOptionsService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VisualStudioChangeSignatureOptionsService( + IClassificationFormatMapService classificationFormatMapService, + ClassificationTypeMap classificationTypeMap, + IThreadingContext threadingContext) : IChangeSignatureOptionsService { - private readonly IClassificationFormatMap _classificationFormatMap; - private readonly ClassificationTypeMap _classificationTypeMap; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioChangeSignatureOptionsService( - IClassificationFormatMapService classificationFormatMapService, - ClassificationTypeMap classificationTypeMap, - IThreadingContext threadingContext) : base(threadingContext) - { - _classificationFormatMap = classificationFormatMapService.GetClassificationFormatMap("tooltip"); - _classificationTypeMap = classificationTypeMap; - } + private readonly IClassificationFormatMap _classificationFormatMap = classificationFormatMapService.GetClassificationFormatMap("tooltip"); + private readonly ClassificationTypeMap _classificationTypeMap = classificationTypeMap; + private readonly IThreadingContext _threadingContext = threadingContext; public ChangeSignatureOptionsResult? GetChangeSignatureOptions( Document document, @@ -35,7 +31,7 @@ public VisualStudioChangeSignatureOptionsService( ISymbol symbol, ParameterConfiguration parameters) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var viewModel = new ChangeSignatureDialogViewModel( parameters, diff --git a/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs b/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs index 3f95f78763006..cc9705260d5f7 100644 --- a/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs +++ b/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs @@ -160,7 +160,7 @@ private async Task> FixUpDescriptors } var span = new TextSpan(descriptor.SpanStart, descriptor.SpanLength); - var results = await spanMapper.MapSpansAsync(document, SpecializedCollections.SingletonEnumerable(span), cancellationToken).ConfigureAwait(false); + var results = await spanMapper.MapSpansAsync(document, [span], cancellationToken).ConfigureAwait(false); // external component violated contracts. the mapper should preserve input order/count. // since we gave in 1 span, it should return 1 span back @@ -207,7 +207,7 @@ private async Task> FixUpDescriptors after2)); } - return list.ToImmutable(); + return list.ToImmutableAndClear(); } private static (string text, int start, int length) GetReferenceInfo(ExcerptResult? reference, ReferenceLocationDescriptor descriptor) diff --git a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs index b629d0bc0b5f9..aeddff70e487b 100644 --- a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs +++ b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.DesignerAttribute; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; @@ -28,10 +29,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute; [ExportEventListener(WellKnownEventListeners.Workspace, WorkspaceKind.Host), Shared] -internal class VisualStudioDesignerAttributeService : - ForegroundThreadAffinitizedObject, IDesignerAttributeDiscoveryService.ICallback, IEventListener, IDisposable +internal sealed class VisualStudioDesignerAttributeService : + IDesignerAttributeDiscoveryService.ICallback, IEventListener, IDisposable { private readonly VisualStudioWorkspaceImpl _workspace; + private readonly IThreadingContext _threadingContext; /// /// Used to acquire the legacy project designer service. @@ -64,9 +66,9 @@ public VisualStudioDesignerAttributeService( IThreadingContext threadingContext, IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider, Shell.SVsServiceProvider serviceProvider) - : base(threadingContext) { _workspace = workspace; + _threadingContext = threadingContext; _serviceProvider = serviceProvider; _listener = asynchronousOperationListenerProvider.GetListener(FeatureAttribute.DesignerAttributes); @@ -75,13 +77,13 @@ public VisualStudioDesignerAttributeService( DelayTimeSpan.Idle, this.ProcessWorkspaceChangeAsync, _listener, - ThreadingContext.DisposalToken); + _threadingContext.DisposalToken); _projectSystemNotificationQueue = new AsyncBatchingWorkQueue( DelayTimeSpan.Idle, this.NotifyProjectSystemAsync, _listener, - ThreadingContext.DisposalToken); + _threadingContext.DisposalToken); } public void Dispose() @@ -177,9 +179,7 @@ private async Task NotifyLegacyProjectSystemAsync( CancellationToken cancellationToken) { // legacy project system can only be talked to on the UI thread. - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - - AssertIsForeground(); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); var designerService = _legacyDesignerService ??= (IVSMDDesignerService)_serviceProvider.GetService(typeof(SVSMDDesignerService)); if (designerService == null) @@ -201,7 +201,7 @@ private void NotifyLegacyProjectSystemOnUIThread( IVsHierarchy hierarchy, DesignerAttributeData data) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var itemId = hierarchy.TryGetItemId(data.FilePath); if (itemId == VSConstants.VSITEMID_NIL) @@ -276,8 +276,7 @@ private static async Task NotifyCpsProjectSystemAsync( { if (!_cpsProjects.TryGetValue(projectId, out var updateService)) { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - this.AssertIsForeground(); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); updateService = ComputeUpdateService(); _cpsProjects.TryAdd(projectId, updateService); diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs index b86cf9b30a438..ba45c8e817d57 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs @@ -23,7 +23,7 @@ internal partial class VisualStudioDiagnosticAnalyzerProvider /// Loads VSIX analyzers into workspaces that provide when they are loaded. /// [Export] - [ExportEventListener(WellKnownEventListeners.Workspace, WorkspaceKind.Host, WorkspaceKind.Interactive), Shared] + [ExportEventListener(WellKnownEventListeners.Workspace, WorkspaceKind.Host, WorkspaceKind.Interactive, WorkspaceKind.SemanticSearch), Shared] internal sealed class WorkspaceEventListener : IEventListener { private readonly IAsynchronousOperationListener _listener; diff --git a/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs index 789dc0c6c2760..7b0668f518478 100644 --- a/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs +++ b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs @@ -120,7 +120,7 @@ public static ImmutableArray CreateDocumentSymbolData(JToken while (currentStart < allSymbols.Length) finalResult.Add(NestDescendantSymbols(allSymbols, currentStart, out currentStart)); - return finalResult.ToImmutable(); + return finalResult.ToImmutableAndClear(); // Returns the symbol in the list at index start (the parent symbol) with the following symbols in the list // (descendants) appropriately nested into the parent. @@ -180,7 +180,7 @@ public static ImmutableArray GetDocumentSymbolItemV SortOption sortOption, ImmutableArray documentSymbolData) { - using var _ = ArrayBuilder.GetInstance(documentSymbolData.Length, out var documentSymbolItems); + var documentSymbolItems = new FixedSizeArrayBuilder(documentSymbolData.Length); foreach (var documentSymbol in documentSymbolData) { var children = GetDocumentSymbolItemViewModels(sortOption, documentSymbol.Children); @@ -189,7 +189,7 @@ public static ImmutableArray GetDocumentSymbolItemV } documentSymbolItems.Sort(DocumentSymbolDataViewModelSorter.GetComparer(sortOption)); - return documentSymbolItems.ToImmutableAndClear(); + return documentSymbolItems.MoveToImmutable(); } public static void SetExpansionOption( @@ -226,7 +226,7 @@ public static ImmutableArray SearchDocumentSymbolData( filteredDocumentSymbols.Add(documentSymbol with { Children = filteredChildren }); } - return filteredDocumentSymbols.ToImmutable(); + return filteredDocumentSymbols.ToImmutableAndClear(); // Returns true if the name of one of the tree nodes results in a pattern match. static bool SearchNodeTree(DocumentSymbolData tree, PatternMatcher patternMatcher, CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/Common/EditorTextUpdater.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/Common/EditorTextUpdater.cs index 75131b8050b90..184802ff869d2 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/Common/EditorTextUpdater.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/Common/EditorTextUpdater.cs @@ -4,10 +4,9 @@ using System.Collections.Generic; using System.Collections.Immutable; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.TextManager.Interop; diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs index 6544889756b73..c5481121e2963 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs @@ -66,7 +66,7 @@ public SettingsEditorControl(ISettingsEditorView whitespaceView, analyzerView ]; - _tableControls = _views.SelectAsArray(view => view.TableControl).ToArray(); + _tableControls = [.. _views.SelectAsArray(view => view.TableControl)]; InitializeComponent(); } diff --git a/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs b/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs index 1497e07f1042c..49794d90e20f2 100644 --- a/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs +++ b/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs @@ -29,13 +29,16 @@ internal partial class VisualStudioErrorReportingService : IErrorReportingServic public VisualStudioErrorReportingService( IThreadingContext threadingContext, IVsService activityLog, - IAsynchronousOperationListenerProvider listenerProvider, - SVsServiceProvider serviceProvider) + IVsService vsInfoBarUIFactory, + IVsService vsShell, + IAsynchronousOperationListenerProvider listenerProvider) { _threadingContext = threadingContext; _activityLog = activityLog; _listener = listenerProvider.GetListener(FeatureAttribute.Workspace); - _infoBar = new VisualStudioInfoBar(threadingContext, serviceProvider, listenerProvider); + + // Attach this info bar to the global shell location for info-bars (independent of any particular window). + _infoBar = new VisualStudioInfoBar(threadingContext, vsInfoBarUIFactory, vsShell, listenerProvider, windowFrame: null); } public string HostDisplayName => "Visual Studio"; @@ -44,7 +47,7 @@ public void ShowGlobalErrorInfo(string message, TelemetryFeatureName featureName { var stackTrace = exception is null ? "" : GetFormattedExceptionStack(exception); LogGlobalErrorToActivityLog(message, stackTrace); - _infoBar.ShowInfoBar(message, items); + _infoBar.ShowInfoBarMessageFromAnyThread(message, items); Logger.Log(FunctionId.VS_ErrorReportingService_ShowGlobalErrorInfo, KeyValueLogMessage.Create(LogType.UserAction, m => { @@ -72,7 +75,7 @@ public void ShowFeatureNotAvailableErrorInfo(string message, TelemetryFeatureNam closeAfterAction: true)); } - ShowGlobalErrorInfo(message, featureName, exception, infoBarUIs.ToArray()); + ShowGlobalErrorInfo(message, featureName, exception, [.. infoBarUIs]); } private void LogGlobalErrorToActivityLog(string message, string? detailedError) diff --git a/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioInfoBar.cs b/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioInfoBar.cs index df66d7e859fd6..b138103b89a10 100644 --- a/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioInfoBar.cs +++ b/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioInfoBar.cs @@ -4,119 +4,175 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Imaging; +using Microsoft.VisualStudio.Imaging.Interop; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ErrorReporting; -internal sealed class VisualStudioInfoBar +internal sealed class VisualStudioInfoBar( + IThreadingContext threadingContext, + IVsService vsInfoBarUIFactory, + IVsService vsShell, + IAsynchronousOperationListenerProvider listenerProvider, + IVsWindowFrame? windowFrame) { - private readonly IThreadingContext _threadingContext; - private readonly SVsServiceProvider _serviceProvider; - private readonly IAsynchronousOperationListener _listener; + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly IVsService _vsInfoBarUIFactory = vsInfoBarUIFactory; + private readonly IVsService _vsShell = vsShell; + private readonly IAsynchronousOperationListener _listener = listenerProvider.GetListener(FeatureAttribute.InfoBar); + private readonly IVsWindowFrame? _windowFrame = windowFrame; /// - /// 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. + /// 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. /// - /// Accessed on UI thread. + /// Accessed on UI thread only. /// private readonly HashSet _currentlyShowingMessages = []; - public VisualStudioInfoBar( - IThreadingContext threadingContext, - SVsServiceProvider serviceProvider, - IAsynchronousOperationListenerProvider listenerProvider) - { - _threadingContext = threadingContext; - _serviceProvider = serviceProvider; - _listener = listenerProvider.GetListener(FeatureAttribute.InfoBar); - } + public void ShowInfoBarMessageFromAnyThread(string message, params InfoBarUI[] items) + => ShowInfoBarMessageFromAnyThread(message, isCloseButtonVisible: true, KnownMonikers.StatusInformation, items); - public void ShowInfoBar(string message, params InfoBarUI[] items) + public void ShowInfoBarMessageFromAnyThread( + string message, + bool isCloseButtonVisible, + ImageMoniker imageMoniker, + params InfoBarUI[] items) { // We can be called from any thread since errors can occur anywhere, however we can only construct and InfoBar from the UI thread. _threadingContext.JoinableTaskFactory.RunAsync(async () => { - using var _ = _listener.BeginAsyncOperation(nameof(ShowInfoBar)); + using var _ = _listener.BeginAsyncOperation(nameof(ShowInfoBarMessageFromAnyThread)); + + await ShowInfoBarMessageAsync(message, isCloseButtonVisible, imageMoniker, items).ConfigureAwait(false); + }); + } + + public async Task ShowInfoBarMessageAsync( + string message, + bool isCloseButtonVisible, + ImageMoniker imageMoniker, + params InfoBarUI[] items) + { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_threadingContext.DisposalToken); + + // 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 (await GetInfoBarHostObjectAsync().ConfigureAwait(true) is not IVsInfoBarHost infoBarHost) + return null; + + if (_currentlyShowingMessages.Contains(message)) + return null; + + // create action item list + var actionItems = new List(); + + foreach (var item in items) + { + switch (item.Kind) + { + case InfoBarUI.UIKind.Button: + actionItems.Add(new InfoBarButton(item.Title)); + break; + case InfoBarUI.UIKind.HyperLink: + actionItems.Add(new InfoBarHyperlink(item.Title)); + break; + case InfoBarUI.UIKind.Close: + break; + default: + throw ExceptionUtilities.UnexpectedValue(item.Kind); + } + } + + var infoBarModel = new InfoBarModel( + new[] { new InfoBarTextSpan(message) }, + actionItems, + imageMoniker, + isCloseButtonVisible); + + var factory = await _vsInfoBarUIFactory.GetValueAsync().ConfigureAwait(true); + var infoBarUI = factory.CreateInfoBar(infoBarModel); + if (infoBarUI == null) + return null; + + uint? infoBarCookie = null; + var eventSink = new InfoBarEvents(items, onClose: () => + { + Contract.ThrowIfFalse(_threadingContext.JoinableTaskContext.IsOnMainThread); - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_threadingContext.DisposalToken); + // Remove the message from the list that we're keeping track of. Future identical + // messages can now be shown. + _currentlyShowingMessages.Remove(message); - // 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) && - _serviceProvider.GetService(typeof(SVsShell)) is IVsShell shell && - ErrorHandler.Succeeded(shell.GetProperty((int)__VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out var globalInfoBar)) && - globalInfoBar is IVsInfoBarHost infoBarHost && - _serviceProvider.GetService(typeof(SVsInfoBarUIFactory)) is IVsInfoBarUIFactory factory) + // Run given onClose action if there is one. + items.FirstOrDefault(i => i.Kind == InfoBarUI.UIKind.Close).Action?.Invoke(); + + if (infoBarCookie.HasValue) { - // create action item list - var actionItems = new List(); - - foreach (var item in items) - { - switch (item.Kind) - { - case InfoBarUI.UIKind.Button: - actionItems.Add(new InfoBarButton(item.Title)); - break; - case InfoBarUI.UIKind.HyperLink: - actionItems.Add(new InfoBarHyperlink(item.Title)); - break; - case InfoBarUI.UIKind.Close: - break; - default: - throw ExceptionUtilities.UnexpectedValue(item.Kind); - } - } - - var infoBarModel = new InfoBarModel( - new[] { new InfoBarTextSpan(message) }, - actionItems, - KnownMonikers.StatusInformation, - isCloseButtonVisible: true); - - var infoBarUI = factory.CreateInfoBar(infoBarModel); - if (infoBarUI == null) - return; - - uint? infoBarCookie = null; - var eventSink = new InfoBarEvents(items, onClose: () => - { - Contract.ThrowIfFalse(_threadingContext.JoinableTaskContext.IsOnMainThread); - - // 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) - { - infoBarUI.Unadvise(infoBarCookie.Value); - } - }); - - infoBarUI.Advise(eventSink, out var cookie); - infoBarCookie = cookie; - - infoBarHost.AddInfoBar(infoBarUI); - - _currentlyShowingMessages.Add(message); + infoBarUI.Unadvise(infoBarCookie.Value); } }); + + if (ErrorHandler.Succeeded(infoBarUI.Advise(eventSink, out var cookie))) + infoBarCookie = cookie; + + infoBarHost.AddInfoBar(infoBarUI); + + _currentlyShowingMessages.Add(message); + return new InfoBarMessage(this, infoBarHost, message, imageMoniker, infoBarUI, infoBarCookie); + } + + private async Task GetInfoBarHostObjectAsync() + { + _threadingContext.ThrowIfNotOnUIThread(); + + if (_windowFrame != null) + return ErrorHandler.Succeeded(_windowFrame.GetProperty((int)__VSFPROPID7.VSFPROPID_InfoBarHost, out var windowInfoBarHostObject)) ? windowInfoBarHostObject : null; + + var shell = await _vsShell.GetValueAsync().ConfigureAwait(true); + return ErrorHandler.Succeeded(shell.GetProperty((int)__VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out var globalInfoBarHostObject)) + ? globalInfoBarHostObject + : null; + } + + public sealed class InfoBarMessage( + VisualStudioInfoBar visualStudioInfoBar, + IVsInfoBarHost infoBarHost, + string message, + ImageMoniker imageMoniker, + IVsInfoBarUIElement infoBarUI, + uint? infoBarCookie) + { + public readonly string Message = message; + public readonly ImageMoniker ImageMoniker = imageMoniker; + + private bool _removed; + + public void Remove() + { + visualStudioInfoBar._threadingContext.ThrowIfNotOnUIThread(); + if (_removed) + return; + + _removed = true; + if (infoBarCookie.HasValue) + infoBarUI.Unadvise(infoBarCookie.Value); + + infoBarHost.RemoveInfoBar(infoBarUI); + visualStudioInfoBar._currentlyShowingMessages.Remove(this.Message); + } } private sealed class InfoBarEvents : IVsInfoBarUIEvents diff --git a/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs b/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs index e0b9f8ea40b6b..1b5848f8ca8d9 100644 --- a/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.DocumentHighlighting; using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -124,7 +125,7 @@ protected AbstractTableDataSourceFindUsagesContext( bool includeKindColumn, IThreadingContext threadingContext) { - presenter.AssertIsForeground(); + presenter.ThreadingContext.ThrowIfNotOnUIThread(); Presenter = presenter; _findReferencesWindow = findReferencesWindow; @@ -206,7 +207,7 @@ protected void NotifyChange() private void OnFindReferencesWindowClosed(object sender, EventArgs e) { - Presenter.AssertIsForeground(); + this.Presenter.ThreadingContext.ThrowIfNotOnUIThread(); CancelSearch(); _findReferencesWindow.Closed -= OnFindReferencesWindowClosed; @@ -215,13 +216,13 @@ private void OnFindReferencesWindowClosed(object sender, EventArgs e) private void OnTableControlGroupingsChanged(object sender, EventArgs e) { - Presenter.AssertIsForeground(); + this.Presenter.ThreadingContext.ThrowIfNotOnUIThread(); UpdateGroupingByDefinition(); } private void UpdateGroupingByDefinition() { - Presenter.AssertIsForeground(); + this.Presenter.ThreadingContext.ThrowIfNotOnUIThread(); var changed = DetermineCurrentGroupingByDefinitionState(); if (changed) @@ -241,7 +242,7 @@ private void UpdateGroupingByDefinition() private bool DetermineCurrentGroupingByDefinitionState() { - Presenter.AssertIsForeground(); + this.Presenter.ThreadingContext.ThrowIfNotOnUIThread(); var definitionColumn = _findReferencesWindow.GetDefinitionColumn(); @@ -256,7 +257,7 @@ private bool DetermineCurrentGroupingByDefinitionState() private void CancelSearch() { - Presenter.AssertIsForeground(); + this.Presenter.ThreadingContext.ThrowIfNotOnUIThread(); // Cancel any in flight find work that is going on. Note: disposal happens in our own // implementation of IDisposable.Dispose. @@ -265,7 +266,7 @@ private void CancelSearch() public void Clear() { - this.Presenter.AssertIsForeground(); + this.Presenter.ThreadingContext.ThrowIfNotOnUIThread(); // Stop all existing work. this.CancelSearch(); @@ -301,7 +302,7 @@ public string SourceTypeIdentifier public IDisposable Subscribe(ITableDataSink sink) { - Presenter.AssertIsForeground(); + this.Presenter.ThreadingContext.ThrowIfNotOnUIThread(); Debug.Assert(_tableDataSink == null); _tableDataSink = sink; @@ -591,7 +592,7 @@ public ITableEntriesSnapshot GetCurrentSnapshot() void IDisposable.Dispose() { - this.Presenter.AssertIsForeground(); + this.Presenter.ThreadingContext.ThrowIfNotOnUIThread(); // VS is letting go of us. i.e. because a new FAR call is happening, or because // of some other event (like the solution being closed). Remove us from the set diff --git a/src/VisualStudio/Core/Def/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs b/src/VisualStudio/Core/Def/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs index fc948d7dc2b9b..810b88b7b1154 100644 --- a/src/VisualStudio/Core/Def/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs @@ -77,7 +77,11 @@ protected override async ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem d using var _ = ArrayBuilder.GetInstance(out var entries); - if (definition.SourceSpans.Length == 1 && definition.MetadataLocations.IsEmpty) + if (definition.SourceSpans.IsEmpty && definition.MetadataLocations.IsEmpty) + { + entries.Add(new NonNavigableDefinitionItemEntry(this, definitionBucket)); + } + else if (definition.SourceSpans.Length == 1 && definition.MetadataLocations.IsEmpty) { // If we only have a single location, then use the DisplayParts of the // definition as what to show. That way we show enough information for things diff --git a/src/VisualStudio/Core/Def/FindReferences/Entries/AbstractDocumentSpanEntry.cs b/src/VisualStudio/Core/Def/FindReferences/Entries/AbstractDocumentSpanEntry.cs index a9a35ae3f98d8..09c5bfd66e9d7 100644 --- a/src/VisualStudio/Core/Def/FindReferences/Entries/AbstractDocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/FindReferences/Entries/AbstractDocumentSpanEntry.cs @@ -81,7 +81,7 @@ await documentNavigationService.TryNavigateToSpanAsync( } var results = await service.MapSpansAsync( - documentSpan.Document, SpecializedCollections.SingletonEnumerable(documentSpan.SourceSpan), cancellationToken).ConfigureAwait(false); + documentSpan.Document, [documentSpan.SourceSpan], cancellationToken).ConfigureAwait(false); if (results.IsDefaultOrEmpty) { diff --git a/src/VisualStudio/Core/Def/FindReferences/Entries/DocumentSpanEntry.cs b/src/VisualStudio/Core/Def/FindReferences/Entries/DocumentSpanEntry.cs index a62c82cec2cad..83798e05f9b34 100644 --- a/src/VisualStudio/Core/Def/FindReferences/Entries/DocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/FindReferences/Entries/DocumentSpanEntry.cs @@ -226,7 +226,7 @@ public override bool TryCreateColumnContent(string columnName, [NotNullWhen(true private DisposableToolTip CreateDisposableToolTip(Document document, TextSpan sourceSpan) { - Presenter.AssertIsForeground(); + this.Presenter.ThreadingContext.ThrowIfNotOnUIThread(); var controlService = document.Project.Solution.Services.GetRequiredService(); var sourceText = document.GetTextSynchronously(CancellationToken.None); @@ -235,7 +235,7 @@ private DisposableToolTip CreateDisposableToolTip(Document document, TextSpan so if (excerptService != null) { var classificationOptions = Presenter._globalOptions.GetClassificationOptions(document.Project.Language); - var excerpt = Presenter.ThreadingContext.JoinableTaskFactory.Run(() => excerptService.TryExcerptAsync(document, sourceSpan, ExcerptMode.Tooltip, classificationOptions, CancellationToken.None)); + var excerpt = this.Presenter.ThreadingContext.JoinableTaskFactory.Run(() => excerptService.TryExcerptAsync(document, sourceSpan, ExcerptMode.Tooltip, classificationOptions, CancellationToken.None)); if (excerpt != null) { // get tooltip from excerpt service diff --git a/src/VisualStudio/Core/Def/FindReferences/Entries/NonNavigableEntry.cs b/src/VisualStudio/Core/Def/FindReferences/Entries/NonNavigableEntry.cs new file mode 100644 index 0000000000000..79b0451e5e74b --- /dev/null +++ b/src/VisualStudio/Core/Def/FindReferences/Entries/NonNavigableEntry.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.Generic; +using System.Windows.Documents; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.VisualStudio.Shell.TableManager; + +namespace Microsoft.VisualStudio.LanguageServices.FindUsages; + +internal partial class StreamingFindUsagesPresenter +{ + private class NonNavigableDefinitionItemEntry(AbstractTableDataSourceFindUsagesContext context, RoslynDefinitionBucket definitionBucket) + : AbstractItemEntry(definitionBucket, context.Presenter) + { + protected override object? GetValueWorker(string keyName) + => keyName switch + { + StandardTableKeyNames.Text => DefinitionBucket.DefinitionItem.DisplayParts.JoinText(), + StandardTableKeyNames.ItemOrigin => ItemOrigin.Exact, + _ => null, + }; + + protected override IList CreateLineTextInlines() + => DefinitionBucket.DefinitionItem.DisplayParts + .ToInlines(Presenter.ClassificationFormatMap, Presenter.TypeMap); + } +} diff --git a/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs b/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs index ad9a525716c26..93ebde5aa7161 100644 --- a/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs +++ b/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs @@ -34,8 +34,7 @@ namespace Microsoft.VisualStudio.LanguageServices.FindUsages; [Export(typeof(IStreamingFindUsagesPresenter)), Shared] -internal partial class StreamingFindUsagesPresenter : - ForegroundThreadAffinitizedObject, IStreamingFindUsagesPresenter +internal sealed partial class StreamingFindUsagesPresenter : IStreamingFindUsagesPresenter { public const string RoslynFindUsagesTableDataSourceIdentifier = nameof(RoslynFindUsagesTableDataSourceIdentifier); @@ -51,6 +50,7 @@ internal partial class StreamingFindUsagesPresenter : private readonly Lazy _lazyClassificationFormatMap; private readonly Workspace _workspace; + private readonly IThreadingContext ThreadingContext; private readonly IGlobalOptionService _globalOptions; private readonly HashSet _currentContexts = []; @@ -114,9 +114,9 @@ private StreamingFindUsagesPresenter( IClassificationFormatMapService classificationFormatMapService, IEnumerable columns, IAsynchronousOperationListenerProvider asyncListenerProvider) - : base(threadingContext, assertIsForeground: false) { _workspace = workspace; + ThreadingContext = threadingContext; _globalOptions = optionService; _serviceProvider = serviceProvider; TypeMap = typeMap; @@ -125,7 +125,7 @@ private StreamingFindUsagesPresenter( _lazyClassificationFormatMap = new Lazy(() => { - AssertIsForeground(); + ThreadingContext.ThrowIfNotOnUIThread(); return classificationFormatMapService.GetClassificationFormatMap("tooltip"); }); @@ -157,7 +157,7 @@ private static IEnumerable GetCustomColumns(IEnumerable< public void ClearAll() { - this.AssertIsForeground(); + ThreadingContext.ThrowIfNotOnUIThread(); foreach (var context in _currentContexts) { @@ -170,7 +170,7 @@ public void ClearAll() /// public (FindUsagesContext context, CancellationToken cancellationToken) StartSearch(string title, StreamingFindUsagesPresenterOptions options) { - this.AssertIsForeground(); + ThreadingContext.ThrowIfNotOnUIThread(); var vsFindAllReferencesService = (IFindAllReferencesService)_serviceProvider.GetService(typeof(SVsFindAllReferences)); @@ -219,7 +219,7 @@ private AbstractTableDataSourceFindUsagesContext StartSearchWithReferences( tableControl.GroupingsChanged += (s, e) => StoreCurrentGroupingPriority(window); return new WithReferencesFindUsagesContext( - this, window, _customColumns, _globalOptions, includeContainingTypeAndMemberColumns, includeKindColumn, this.ThreadingContext); + this, window, _customColumns, _globalOptions, includeContainingTypeAndMemberColumns, includeKindColumn, ThreadingContext); } private AbstractTableDataSourceFindUsagesContext StartSearchWithoutReferences( @@ -230,7 +230,7 @@ private AbstractTableDataSourceFindUsagesContext StartSearchWithoutReferences( // with the same items showing underneath them. SetDefinitionGroupingPriority(window, 0); return new WithoutReferencesFindUsagesContext( - this, window, _customColumns, _globalOptions, includeContainingTypeAndMemberColumns, includeKindColumn, this.ThreadingContext); + this, window, _customColumns, _globalOptions, includeContainingTypeAndMemberColumns, includeKindColumn, ThreadingContext); } private void StoreCurrentGroupingPriority(IFindAllReferencesWindow window) @@ -240,7 +240,7 @@ private void StoreCurrentGroupingPriority(IFindAllReferencesWindow window) private void SetDefinitionGroupingPriority(IFindAllReferencesWindow window, int priority) { - this.AssertIsForeground(); + ThreadingContext.ThrowIfNotOnUIThread(); using var _ = ArrayBuilder.GetInstance(out var newColumns); var tableControl = (IWpfTableControl2)window.TableControl; @@ -267,7 +267,7 @@ private void SetDefinitionGroupingPriority(IFindAllReferencesWindow window, int tableControl.SetColumnStates(newColumns); } - protected static (Guid, string projectName, string? projectFlavor) GetGuidAndProjectInfo(Document document) + private 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 @@ -287,7 +287,7 @@ protected static (Guid, string projectName, string? projectFlavor) GetGuidAndPro private void RemoveExistingInfoBar() { - this.ThreadingContext.ThrowIfNotOnUIThread(); + ThreadingContext.ThrowIfNotOnUIThread(); var infoBar = _infoBar; _infoBar = null; @@ -300,7 +300,7 @@ private void RemoveExistingInfoBar() private IVsInfoBarHost? GetInfoBarHost() { - this.ThreadingContext.ThrowIfNotOnUIThread(); + ThreadingContext.ThrowIfNotOnUIThread(); // Guid of the FindRefs window. Defined here: // https://devdiv.visualstudio.com/DevDiv/_git/VS?path=/src/env/ErrorList/Pkg/Guids.cs&version=GBmain&line=24 @@ -320,7 +320,7 @@ private void RemoveExistingInfoBar() private async Task ReportMessageAsync(string message, NotificationSeverity severity, CancellationToken cancellationToken) { - await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); RemoveExistingInfoBar(); if (_serviceProvider.GetService(typeof(SVsInfoBarUIFactory)) is not IVsInfoBarUIFactory factory) diff --git a/src/VisualStudio/Core/Def/GenerateType/GenerateTypeDialogViewModel.cs b/src/VisualStudio/Core/Def/GenerateType/GenerateTypeDialogViewModel.cs index df030454939a5..e0f71c4f4d29d 100644 --- a/src/VisualStudio/Core/Def/GenerateType/GenerateTypeDialogViewModel.cs +++ b/src/VisualStudio/Core/Def/GenerateType/GenerateTypeDialogViewModel.cs @@ -781,7 +781,7 @@ internal GenerateTypeDialogViewModel( } else { - this.ProjectFolders = SpecializedCollections.EmptyList(); + this.ProjectFolders = []; } } diff --git a/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs b/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs new file mode 100644 index 0000000000000..d04d43a5cd83c --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation; + +[Export(typeof(IUIContextActivationService)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VisualStudioUIContextActivationService() : IUIContextActivationService +{ + public void ExecuteWhenActivated(Guid uiContext, Action action) + { + var context = UIContext.FromUIContextGuid(uiContext); + context.WhenActivated(action); + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs b/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs index 2cc9eb4b5c06b..a2a015311f028 100644 --- a/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs +++ b/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs @@ -46,7 +46,7 @@ public bool TryOnBeforeGlobalSymbolRenamed(Workspace workspace, IEnumerable /// Manager controls all the glyphs of Inheritance Margin in . /// -internal partial class InheritanceGlyphManager : ForegroundThreadAffinitizedObject, IDisposable +internal sealed partial class InheritanceGlyphManager : IDisposable { // We want to our glyphs to have the same background color as the glyphs in GlyphMargin. private const string GlyphMarginName = "Indicator Margin"; @@ -55,7 +56,7 @@ public InheritanceGlyphManager( IEditorFormatMap editorFormatMap, IAsynchronousOperationListener listener, Canvas canvas, - double heightAndWidthOfTheGlyph) : base(threadingContext) + double heightAndWidthOfTheGlyph) { _workspace = workspace; _textView = textView; @@ -85,7 +86,7 @@ void IDisposable.Dispose() /// public void AddGlyph(InheritanceMarginTag tag, SnapshotSpan span) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var lines = _textView.TextViewLines; if (lines.IntersectsBufferSpan(span) && GetStartingLine(lines, span) is IWpfTextViewLine line) { @@ -102,7 +103,7 @@ public void AddGlyph(InheritanceMarginTag tag, SnapshotSpan span) /// public void RemoveGlyphs(SnapshotSpan snapshotSpan) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var glyphDataToRemove = _glyphDataTree.GetIntervalsThatIntersectWith(snapshotSpan.Start, snapshotSpan.Length); foreach (var (_, glyph) in glyphDataToRemove) { @@ -124,7 +125,7 @@ public void RemoveGlyphs(SnapshotSpan snapshotSpan) /// public void SetSnapshotAndUpdate(ITextSnapshot snapshot, IList newOrReformattedLines, IList translatedLines) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (!_glyphDataTree.IsEmpty()) { // Go through all the existing visuals and invalidate or transform as appropriate. @@ -212,7 +213,7 @@ private void FormatMappingChanged(object sender, FormatItemsEventArgs e) private void UpdateBackgroundColor() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var resourceDictionary = _editorFormatMap.GetProperties(GlyphMarginName); if (resourceDictionary.Contains(EditorFormatDefinition.BackgroundColorId)) { diff --git a/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginHelpers.cs b/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginHelpers.cs index 786b749e85cc1..b9764f5656baa 100644 --- a/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginHelpers.cs +++ b/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginHelpers.cs @@ -191,6 +191,6 @@ public static ImmutableArray CreateMenuItemsWithHeader( builder.Add(TargetMenuItemViewModel.Create(target, target.DisplayName)); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } diff --git a/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginTaggerProvider.cs b/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginTaggerProvider.cs index f3d1a2a29c881..8b6808ef4df2d 100644 --- a/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginTaggerProvider.cs +++ b/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginTaggerProvider.cs @@ -54,6 +54,12 @@ public InheritanceMarginTaggerProvider( protected override TaggerDelay EventChangeDelay => TaggerDelay.OnIdle; + /// + /// We support frozen partial semantics, so we can quickly get inheritance margin items without building SG docs. + /// We will still run a tagging pass after the frozen-pass where we run again on non-frozen docs. + /// + protected override bool SupportsFrozenPartialSemantics => true; + protected override bool CanCreateTagger(ITextView textView, ITextBuffer buffer) { // Match criterion InheritanceMarginViewMarginProvider uses to determine whether @@ -96,17 +102,13 @@ protected override async Task ProduceTagsAsync( var includeGlobalImports = GlobalOptions.GetOption(InheritanceMarginOptionsStorage.InheritanceMarginIncludeGlobalImports, document.Project.Language); - // 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) - document = document.WithFrozenPartialSemantics(cancellationToken); - var spanToSearch = spanToTag.SnapshotSpan.Span.ToTextSpan(); var stopwatch = SharedStopwatch.StartNew(); var inheritanceMemberItems = await inheritanceMarginInfoService.GetInheritanceMemberItemsAsync( document, spanToSearch, includeGlobalImports, - frozenPartialSemantics: true, + context.FrozenPartialSemantics, cancellationToken).ConfigureAwait(false); var elapsed = stopwatch.Elapsed; diff --git a/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginViewMargin.cs b/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginViewMargin.cs index 9449fe3b0721d..ce2aa66a4f75e 100644 --- a/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginViewMargin.cs +++ b/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginViewMargin.cs @@ -7,10 +7,9 @@ using System.Linq; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.InheritanceMargin; using Microsoft.CodeAnalysis.Options; @@ -24,11 +23,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin; -internal class InheritanceMarginViewMargin : ForegroundThreadAffinitizedObject, IWpfTextViewMargin +internal sealed class InheritanceMarginViewMargin : IWpfTextViewMargin { // 16 (width of the crisp image) + 2 * 1 (width of the border) = 18 private const double HeightAndWidthOfMargin = 18; private readonly IWpfTextView _textView; + private readonly IThreadingContext _threadingContext; private readonly ITagAggregator _tagAggregator; private readonly IGlobalOptionService _globalOptions; private readonly InheritanceGlyphManager _glyphManager; @@ -54,9 +54,10 @@ public InheritanceMarginViewMargin( IEditorFormatMap editorFormatMap, IGlobalOptionService globalOptions, IAsynchronousOperationListener listener, - string languageName) : base(threadingContext) + string languageName) { _textView = textView; + _threadingContext = threadingContext; _tagAggregator = tagAggregator; _globalOptions = globalOptions; _languageName = languageName; @@ -86,7 +87,7 @@ public InheritanceMarginViewMargin( void IDisposable.Dispose() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (!_disposed) { _disposed = true; diff --git a/src/VisualStudio/Core/Def/Interop/CleanableWeakComHandleTable.cs b/src/VisualStudio/Core/Def/Interop/CleanableWeakComHandleTable.cs index efa3beb15f382..94d7f017396c4 100644 --- a/src/VisualStudio/Core/Def/Interop/CleanableWeakComHandleTable.cs +++ b/src/VisualStudio/Core/Def/Interop/CleanableWeakComHandleTable.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; @@ -21,14 +22,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Interop; /// logic for cleaning up dead references in a time-sliced way. Public members of this /// collection are affinitized to the foreground thread. /// -internal class CleanableWeakComHandleTable : ForegroundThreadAffinitizedObject - where TValue : class +internal sealed class CleanableWeakComHandleTable where TValue : class { private const int DefaultCleanUpThreshold = 25; private static readonly TimeSpan s_defaultCleanUpTimeSlice = TimeSpan.FromMilliseconds(15); private readonly Dictionary> _table; private readonly HashSet _deadKeySet; + private readonly IThreadingContext _threadingContext; /// /// The upper limit of items that the collection will store before clean up is recommended. @@ -46,11 +47,10 @@ internal class CleanableWeakComHandleTable : ForegroundThreadAffin public bool NeedsCleanUp => _needsCleanUp; public CleanableWeakComHandleTable(IThreadingContext threadingContext, int? cleanUpThreshold = null, TimeSpan? cleanUpTimeSlice = null) - : base(threadingContext) { _table = []; _deadKeySet = []; - + _threadingContext = threadingContext; CleanUpThreshold = cleanUpThreshold ?? DefaultCleanUpThreshold; CleanUpTimeSlice = cleanUpTimeSlice ?? s_defaultCleanUpTimeSlice; } @@ -63,9 +63,9 @@ public async Task CleanUpDeadObjectsAsync(IAsynchronousOperationListener listene { 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."); + 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); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_threadingContext.DisposalToken); if (!_needsCleanUp) { @@ -137,14 +137,14 @@ async Task RemoveDeadKeysAsync() async Task ResetTimeSliceAsync() { - await listener.Delay(DelayTimeSpan.NearImmediate, ThreadingContext.DisposalToken).ConfigureAwait(true); + await listener.Delay(DelayTimeSpan.NearImmediate, _threadingContext.DisposalToken).ConfigureAwait(true); timeSlice = new TimeSlice(CleanUpTimeSlice); } } public void Add(TKey key, TValue value) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (value == null) { @@ -168,7 +168,7 @@ public void Add(TKey key, TValue value) public TValue Remove(TKey key) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); _deadKeySet.Remove(key); @@ -183,14 +183,14 @@ public TValue Remove(TKey key) public bool ContainsKey(TKey key) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); return _table.ContainsKey(key); } public bool TryGetValue(TKey key, out TValue value) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_table.TryGetValue(key, out var handle)) { value = handle.ComAggregateObject; diff --git a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs index 256599369a73d..0711327e2ebf1 100644 --- a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs +++ b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs @@ -10,15 +10,13 @@ using System.Runtime.InteropServices; 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.Extensions; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.VisualStudio.LanguageServices.Implementation; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; -using Microsoft.VisualStudio.LanguageServices.Utilities; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.PlatformUI.OleComponentSupport; using Microsoft.VisualStudio.Shell; @@ -44,7 +42,7 @@ namespace Microsoft.VisualStudio.LanguageServices.KeybindingReset; /// If we find other extensions that do this in the future, we'll re-use this same mechanism /// [Export(typeof(KeybindingResetDetector))] -internal sealed class KeybindingResetDetector : ForegroundThreadAffinitizedObject, IOleCommandTarget +internal sealed class KeybindingResetDetector : IOleCommandTarget { private const string KeybindingsFwLink = "https://go.microsoft.com/fwlink/?linkid=864209"; private const string ReSharperExtensionName = "ReSharper Ultimate"; @@ -60,7 +58,7 @@ internal sealed class KeybindingResetDetector : ForegroundThreadAffinitizedObjec private static readonly Guid s_resharperCommandGroup = new("47F03277-5055-4922-899C-0F7F30D26BF1"); private static readonly ImmutableArray s_statusOptions = [new OptionKey2(KeybindingResetOptionsStorage.ReSharperStatus), new OptionKey2(KeybindingResetOptionsStorage.NeedsReset)]; - + private readonly IThreadingContext _threadingContext; private readonly IGlobalOptionService _globalOptions; private readonly System.IServiceProvider _serviceProvider; private readonly VisualStudioInfoBar _infoBar; @@ -90,29 +88,32 @@ internal sealed class KeybindingResetDetector : ForegroundThreadAffinitizedObjec public KeybindingResetDetector( IThreadingContext threadingContext, IGlobalOptionService globalOptions, + IVsService vsInfoBarUIFactory, + IVsService vsShell, SVsServiceProvider serviceProvider, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext) { + _threadingContext = threadingContext; _globalOptions = globalOptions; _serviceProvider = serviceProvider; - _infoBar = new VisualStudioInfoBar(threadingContext, serviceProvider, listenerProvider); + + // Attach this info bar to the global shell location for info-bars (independent of any particular window). + _infoBar = new VisualStudioInfoBar(threadingContext, vsInfoBarUIFactory, vsShell, listenerProvider, windowFrame: null); } - public Task InitializeAsync() + public async Task InitializeAsync(CancellationToken cancellationToken) { // Immediately bail if the user has asked to never see this bar again. if (_globalOptions.GetOption(KeybindingResetOptionsStorage.NeverShowAgain)) - { - return Task.CompletedTask; - } + return; - return InvokeBelowInputPriorityAsync(InitializeCore); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + InitializeCore(); } private void InitializeCore() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (!_globalOptions.GetOption(KeybindingResetOptionsStorage.EnabledFeatureFlag)) { @@ -239,7 +240,7 @@ private void ShowGoldBar() var message = ServicesVSResources.We_notice_you_suspended_0_Reset_keymappings_to_continue_to_navigate_and_refactor; KeybindingsResetLogger.Log("InfoBarShown"); - _infoBar.ShowInfoBar( + _infoBar.ShowInfoBarMessageFromAnyThread( string.Format(message, ReSharperExtensionName), new InfoBarUI(title: ServicesVSResources.Reset_Visual_Studio_default_keymapping, kind: InfoBarUI.UIKind.Button, @@ -312,7 +313,7 @@ async Task QueryStatusAsync(uint cmdId) cmds[0].cmdID = cmdId; cmds[0].cmdf = 0; - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var hr = _oleCommandTarget.QueryStatus(s_resharperCommandGroup, (uint)cmds.Length, cmds, IntPtr.Zero); if (ErrorHandler.Failed(hr)) @@ -333,7 +334,7 @@ async Task EnsureOleCommandTargetAsync() return; } - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); _oleCommandTarget = _serviceProvider.GetServiceOnMainThread(); } @@ -341,7 +342,7 @@ async Task EnsureOleCommandTargetAsync() private void RestoreVsKeybindings() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); _uiShell ??= _serviceProvider.GetServiceOnMainThread(); @@ -358,7 +359,7 @@ private void RestoreVsKeybindings() private void OpenExtensionsHyperlink() { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); VisualStudioNavigateToLinkService.StartBrowser(KeybindingsFwLink); @@ -373,19 +374,20 @@ private void NeverShowAgain() KeybindingsResetLogger.Log("NeverShowAgain"); // The only external references to this object are as callbacks, which are removed by the Shutdown method. - ThreadingContext.JoinableTaskFactory.Run(ShutdownAsync); + _threadingContext.JoinableTaskFactory.Run(ShutdownAsync); } private void InfoBarClose() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); _infoBarOpen = false; } public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) { // Technically can be called on any thread, though VS will only ever call it on the UI thread. - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); + // We don't care about query status, only when the command is actually executed return (int)OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED; } @@ -393,7 +395,8 @@ public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, Int public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { // Technically can be called on any thread, though VS will only ever call it on the UI thread. - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); + if (pguidCmdGroup == s_resharperCommandGroup && nCmdID >= ResumeId && nCmdID <= ToggleSuspendId) { // Don't delay command processing to update resharper status @@ -406,7 +409,7 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv private void OnModalStateChanged(object sender, StateChangedEventArgs args) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); // Only monitor for StateTransitionType.Exit. This will be fired when the shell is leaving a modal state, including // Tools->Options being exited. This will fire more than just on Options close, but there's no harm from running an @@ -422,7 +425,7 @@ private async Task ShutdownAsync() // we are shutting down, cancel any pending work. _cancellationTokenSource.Cancel(); - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); if (_priorityCommandTargetCookie != VSConstants.VSCOOKIE_NIL) { diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs index c77fb462cebe8..e097e22eeee74 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs @@ -70,7 +70,7 @@ private int FormatWorker(IVsTextLayer textLayer, TextSpan[] selections, Cancella // use formatting that return text changes rather than tree rewrite which is more expensive var formatter = document.GetRequiredLanguageService(); - var originalChanges = formatter.GetFormattingResult(root, SpecializedCollections.SingletonEnumerable(adjustedSpan), formattingOptions, rules, cancellationToken) + var originalChanges = formatter.GetFormattingResult(root, [adjustedSpan], formattingOptions, rules, cancellationToken) .GetTextChanges(cancellationToken); var originalSpan = RoslynTextSpan.FromBounds(start, end); diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs index afcb7efb3456c..16af81de4caf5 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs @@ -4,8 +4,11 @@ using System; using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; using Task = System.Threading.Tasks.Task; namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService; @@ -26,12 +29,25 @@ internal IComponentModel ComponentModel protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { await base.InitializeAsync(cancellationToken, progress).ConfigureAwait(true); + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); _componentModel_doNotAccessDirectly = (IComponentModel)await GetServiceAsync(typeof(SComponentModel)).ConfigureAwait(true); Assumes.Present(_componentModel_doNotAccessDirectly); } + protected override Task OnAfterPackageLoadedAsync(CancellationToken cancellationToken) + { + // TODO: remove, workaround for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1985204 + var globalOptions = ComponentModel.GetService(); + if (globalOptions.GetOption(SemanticSearchFeatureFlag.Enabled)) + { + UIContext.FromUIContextGuid(new Guid(SemanticSearchFeatureFlag.UIContextId)).IsActive = true; + } + + return base.OnAfterPackageLoadedAsync(cancellationToken); + } + protected async Task LoadComponentsInUIContextOnceSolutionFullyLoadedAsync(CancellationToken cancellationToken) { // UIContexts can be "zombied" if UIContexts aren't supported because we're in a command line build or in other scenarios. diff --git a/src/VisualStudio/Core/Def/Library/ClassView/AbstractSyncClassViewCommandHandler.cs b/src/VisualStudio/Core/Def/Library/ClassView/AbstractSyncClassViewCommandHandler.cs index f8f617b024c41..32d8aebbf658c 100644 --- a/src/VisualStudio/Core/Def/Library/ClassView/AbstractSyncClassViewCommandHandler.cs +++ b/src/VisualStudio/Core/Def/Library/ClassView/AbstractSyncClassViewCommandHandler.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Commanding; -using Microsoft.VisualStudio.LanguageServices.Utilities; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; @@ -19,11 +18,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Library.ClassView; -internal abstract class AbstractSyncClassViewCommandHandler : ForegroundThreadAffinitizedObject, - ICommandHandler +internal abstract class AbstractSyncClassViewCommandHandler : ICommandHandler { private const string ClassView = "Class View"; - + private readonly IThreadingContext _threadingContext; private readonly IServiceProvider _serviceProvider; public string DisplayName => ServicesVSResources.Sync_Class_View; @@ -31,16 +29,15 @@ internal abstract class AbstractSyncClassViewCommandHandler : ForegroundThreadAf protected AbstractSyncClassViewCommandHandler( IThreadingContext threadingContext, SVsServiceProvider serviceProvider) - : base(threadingContext) { Contract.ThrowIfNull(serviceProvider); - + _threadingContext = threadingContext; _serviceProvider = serviceProvider; } public bool ExecuteCommand(SyncClassViewCommandArgs args, CommandExecutionContext context) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var caretPosition = args.TextView.GetCaretPoint(args.SubjectBuffer) ?? -1; if (caretPosition < 0) diff --git a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs index cc6478b12b1c2..5f8bbbc47aac1 100644 --- a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs +++ b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs @@ -174,7 +174,7 @@ private static ImmutableArray CreateListItemsFromSymbols( @@ -283,7 +283,7 @@ public ImmutableArray GetFolderListItems(ObjectListItem parentLi } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private ImmutableArray GetMemberListItems( @@ -309,7 +309,7 @@ private ImmutableArray GetMemberListItems( AddListItemsFromSymbols(inheritedMembers, compilation, projectId, CreateInheritedMemberListItem, builder); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private static ImmutableArray GetMemberSymbols(INamedTypeSymbol namedTypeSymbol, Compilation compilation) @@ -325,7 +325,7 @@ private static ImmutableArray GetMemberSymbols(INamedTypeSymbol namedTy } } - return symbolBuilder.ToImmutable(); + return symbolBuilder.ToImmutableAndClear(); } private static ImmutableArray GetInheritedMemberSymbols(INamedTypeSymbol namedTypeSymbol, Compilation compilation) @@ -367,7 +367,7 @@ private static ImmutableArray GetInheritedMemberSymbols(INamedTypeSymbo } } - return symbolBuilder.ToImmutable(); + return symbolBuilder.ToImmutableAndClear(); } private static void AddOverriddenMembers(INamedTypeSymbol namedTypeSymbol, ref HashSet overriddenMembers) @@ -405,13 +405,11 @@ public void CollectNamespaceListItems(IAssemblySymbol assemblySymbol, ProjectId { Debug.Assert(assemblySymbol != null); - var stack = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(assemblySymbol.GlobalNamespace); - while (stack.Count > 0) + while (stack.TryPop(out var namespaceSymbol)) { - var namespaceSymbol = stack.Pop(); - // Only add non-global namespaces that contain accessible type symbols. if (!namespaceSymbol.IsGlobalNamespace && ContainsAccessibleTypeMember(namespaceSymbol, assemblySymbol)) @@ -451,7 +449,7 @@ public ImmutableArray GetNamespaceListItems(ObjectListItem paren CollectNamespaceListItems(assemblySymbol, parentListItem.ProjectId, builder, searchString: null); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private class AssemblySymbolComparer : IEqualityComparer> @@ -562,7 +560,7 @@ private static ImmutableArray GetAccessibleTypeMembers(INamesp } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private static bool IncludeTypeMember(INamedTypeSymbol typeMember, IAssemblySymbol assemblySymbol) @@ -636,7 +634,7 @@ public ImmutableArray GetProjectListItems(Solution solution, str projectListItemBuilder.AddRange(referenceListItemBuilder); - return projectListItemBuilder.ToImmutable(); + return projectListItemBuilder.ToImmutableAndClear(); } public ImmutableArray GetReferenceListItems(ObjectListItem parentListItem, Compilation compilation) @@ -668,16 +666,14 @@ private static ImmutableArray GetAccessibleTypes(INamespaceSym { var typeMembers = GetAccessibleTypeMembers(namespaceSymbol, compilation.Assembly); var builder = ImmutableArray.CreateBuilder(typeMembers.Length); - var stack = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var stack); foreach (var typeMember in typeMembers) { stack.Push(typeMember); - while (stack.Count > 0) + while (stack.TryPop(out var typeSymbol)) { - var typeSymbol = stack.Pop(); - builder.Add(typeSymbol); foreach (var nestedTypeMember in GetAccessibleTypeMembers(typeSymbol, compilation.Assembly)) @@ -687,7 +683,7 @@ private static ImmutableArray GetAccessibleTypes(INamespaceSym } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private ImmutableArray GetTypeListItems( @@ -718,7 +714,7 @@ private ImmutableArray GetTypeListItems( } } - return finalBuilder.ToImmutable(); + return finalBuilder.ToImmutableAndClear(); } public ImmutableArray GetTypeListItems(ObjectListItem parentListItem, Compilation compilation) @@ -751,12 +747,11 @@ public void CollectTypeListItems(IAssemblySymbol assemblySymbol, Compilation com Debug.Assert(assemblySymbol != null); Debug.Assert(compilation != null); - var stack = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(assemblySymbol.GlobalNamespace); - while (stack.Count > 0) + while (stack.TryPop(out var namespaceSymbol)) { - var namespaceSymbol = stack.Pop(); var typeListItems = GetTypeListItems(namespaceSymbol, compilation, projectId, searchString, fullyQualified: true); foreach (var typeListItem in typeListItems) @@ -784,12 +779,11 @@ public void CollectMemberListItems(IAssemblySymbol assemblySymbol, Compilation c Debug.Assert(assemblySymbol != null); Debug.Assert(compilation != null); - var namespaceStack = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var namespaceStack); namespaceStack.Push(assemblySymbol.GlobalNamespace); - while (namespaceStack.Count > 0) + while (namespaceStack.TryPop(out var namespaceSymbol)) { - var namespaceSymbol = namespaceStack.Pop(); var types = GetAccessibleTypes(namespaceSymbol, compilation); foreach (var type in types) diff --git a/src/VisualStudio/Core/Def/Library/VsNavInfo/NavInfo.cs b/src/VisualStudio/Core/Def/Library/VsNavInfo/NavInfo.cs index 12578c5fe84c1..5e6e6808b042c 100644 --- a/src/VisualStudio/Core/Def/Library/VsNavInfo/NavInfo.cs +++ b/src/VisualStudio/Core/Def/Library/VsNavInfo/NavInfo.cs @@ -59,7 +59,7 @@ public NavInfo( _basePresentationNodes = CreateNodes(expandDottedNames: false); _symbolType = _basePresentationNodes.Length > 0 - ? _basePresentationNodes[_basePresentationNodes.Length - 1].ListType + ? _basePresentationNodes[^1].ListType : 0; } diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 7e04d4e2f999b..dbe2f9e7d4870 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -164,19 +164,15 @@ - - - - - - - + + + diff --git a/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs index 1c51b4865aa4b..cf0b5b0f3cc8b 100644 --- a/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs +++ b/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs @@ -3,9 +3,11 @@ // 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.Shared.Extensions; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Search.Data; using Microsoft.VisualStudio.Text.PatternMatching; @@ -21,13 +23,16 @@ internal sealed partial class RoslynSearchItemsSourceProvider /// private sealed class RoslynNavigateToSearchCallback : INavigateToSearchCallback { + private readonly Solution _solution; private readonly RoslynSearchItemsSourceProvider _provider; private readonly ISearchCallback _searchCallback; public RoslynNavigateToSearchCallback( + Solution solution, RoslynSearchItemsSourceProvider provider, ISearchCallback searchCallback) { + _solution = solution; _provider = provider; _searchCallback = searchCallback; } @@ -51,30 +56,34 @@ public void ReportIncomplete() _searchCallback.ReportIncomplete(IncompleteReason.Parsing); } - public Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { // Convert roslyn pattern matches to the platform type. - var matches = result.Matches.SelectAsArray(static m => new PatternMatch( + foreach (var result in results) + { + var matches = result.Matches.SelectAsArray(static m => new PatternMatch( ConvertKind(m.Kind), punctuationStripped: false, m.IsCaseSensitive, m.MatchedSpans.SelectAsArray(static s => s.ToSpan()))); - // Weight the items based on the overall pattern matching weights. We want the items that have the best - // pattern matches (low .Kind values) to have the highest float values (as higher is better for the VS - // api). - var perProviderItemPriority = float.MaxValue - Enumerable.Sum(result.Matches.Select(m => (int)m.Kind)); + // Weight the items based on the overall pattern matching weights. We want the items that have the best + // pattern matches (low .Kind values) to have the highest float values (as higher is better for the VS + // api). + var perProviderItemPriority = float.MaxValue - Enumerable.Sum(result.Matches.Select(m => (int)m.Kind)); - _searchCallback.AddItem(new RoslynCodeSearchResult( - _provider, - result, - GetResultType(result.Kind), - result.Name, - result.SecondarySort, - matches, - result.NavigableItem.Document.FilePath, - perProviderItemPriority, - project.Language)); + var project = _solution.GetRequiredProject(result.NavigableItem.Document.Project.Id); + _searchCallback.AddItem(new RoslynCodeSearchResult( + _provider, + result, + GetResultType(result.Kind), + result.Name, + result.SecondarySort, + matches, + result.NavigableItem.Document.FilePath, + perProviderItemPriority, + project.Language)); + } return Task.CompletedTask; } diff --git a/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs b/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs index cab89f1b13218..c0d8b86af61c9 100644 --- a/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs +++ b/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs @@ -60,14 +60,17 @@ public override async Task PerformSearchAsync(ISearchQuery searchQuery, ISearchC } } - private async Task PerformSearchWorkerAsync(ISearchQuery searchQuery, ISearchCallback searchCallback, CancellationToken cancellationToken) + private async Task PerformSearchWorkerAsync( + ISearchQuery searchQuery, + ISearchCallback searchCallback, + CancellationToken cancellationToken) { var searchValue = searchQuery.QueryString.Trim(); if (string.IsNullOrWhiteSpace(searchValue)) return; - var includeTypeResults = searchQuery.FiltersStates.Any(f => f is { Key: "Types", Value: "True" }); - var includeMembersResults = searchQuery.FiltersStates.Any(f => f is { Key: "Members", Value: "True" }); + var includeTypeResults = searchQuery.FiltersStates.TryGetValue("Types", out var typesValue) && typesValue == "True"; + var includeMembersResults = searchQuery.FiltersStates.TryGetValue("Members", out var membersValue) && membersValue == "True"; var kinds = (includeTypeResults, includeMembersResults) switch { @@ -76,21 +79,26 @@ private async Task PerformSearchWorkerAsync(ISearchQuery searchQuery, ISearchCal _ => s_allKinds, }; - // TODO(cyrusn): New aiosp doesn't seem to support only searching current document. - var searchCurrentDocument = false; + var searchScope = searchQuery switch + { + ICodeSearchQuery { Scope: SearchScopes.CurrentDocument } => NavigateToSearchScope.Document, + ICodeSearchQuery { Scope: SearchScopes.CurrentProject } => NavigateToSearchScope.Project, + _ => NavigateToSearchScope.Solution, + }; // Create a nav-to callback that will take results and translate them to aiosp results for the // callback passed to us. + var solution = provider._workspace.CurrentSolution; var searcher = NavigateToSearcher.Create( - provider._workspace.CurrentSolution, + solution, provider._asyncListener, - new RoslynNavigateToSearchCallback(provider, searchCallback), + new RoslynNavigateToSearchCallback(solution, provider, searchCallback), searchValue, kinds, provider._threadingContext.DisposalToken); - await searcher.SearchAsync(searchCurrentDocument, cancellationToken).ConfigureAwait(false); + await searcher.SearchAsync(searchScope, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/VisualStudio/Core/Def/NavigationBar/NavigationBarClient.cs b/src/VisualStudio/Core/Def/NavigationBar/NavigationBarClient.cs index f03baffe127f4..8597fe3e621cd 100644 --- a/src/VisualStudio/Core/Def/NavigationBar/NavigationBarClient.cs +++ b/src/VisualStudio/Core/Def/NavigationBar/NavigationBarClient.cs @@ -52,8 +52,8 @@ public NavigationBarClient( _workspace = workspace; _imageService = (IVsImageService2)serviceProvider.GetService(typeof(SVsImageService)); - _projectItems = SpecializedCollections.EmptyList(); - _currentTypeItems = SpecializedCollections.EmptyList(); + _projectItems = []; + _currentTypeItems = []; } private NavigationBarItem? GetCurrentTypeItem() diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index 8b5162180b5d2..8841c4fa3071a 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -4,17 +4,10 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Options; -using Microsoft.VisualStudio.Language.Intellisense; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Options; @@ -130,6 +123,7 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_show_outlining_for_declaration_level_constructs", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.ShowOutliningForDeclarationLevelConstructs")}, {"csharp_format_on_close_brace", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.Auto Formatting On Close Brace")}, {"dotnet_classify_reassigned_variables", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.ClassificationOptions.ClassifyReassignedVariables")}, + {"dotnet_classify_obsolete_symbols", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.ClassificationOptions.ClassifyObsoleteSymbols")}, {"dotnet_prefer_system_hash_code", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.PreferSystemHashCode")}, {"visual_studio_color_scheme_name", new RoamingProfileStorage("TextEditor.Roslyn.ColorSchemeName")}, {"visual_studio_color_scheme_use_legacy_enhanced_colors", new RoamingProfileStorage("WindowManagement.Options.UseEnhancedColorsForManagedLanguages")}, @@ -170,6 +164,7 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"csharp_prefer_simple_default_expression", new RoamingProfileStorage("TextEditor.CSharp.Specific.PreferSimpleDefaultExpression")}, {"csharp_prefer_simple_using_statement", new RoamingProfileStorage("TextEditor.CSharp.Specific.PreferSimpleUsingStatement")}, {"csharp_prefer_static_local_function", new RoamingProfileStorage("TextEditor.CSharp.Specific.PreferStaticLocalFunction")}, + {"csharp_prefer_static_anonymous_function", new RoamingProfileStorage("TextEditor.CSharp.Specific.PreferStaticAnonymousFunction")}, {"csharp_preferred_modifier_order", new RoamingProfileStorage("TextEditor.CSharp.Specific.PreferredModifierOrder")}, {"csharp_preserve_single_line_blocks", new RoamingProfileStorage("TextEditor.CSharp.Specific.WrappingPreserveSingleLine")}, {"csharp_preserve_single_line_statements", new RoamingProfileStorage("TextEditor.CSharp.Specific.WrappingKeepStatementsOnSingleLine")}, @@ -240,8 +235,6 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_provide_date_and_time_completions", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.ProvideDateAndTimeCompletions")}, {"dotnet_log_telemetry_for_background_analyzer_execution", new FeatureFlagStorage(@"Roslyn.LogTelemetryForBackgroundAnalyzerExecution")}, {"dotnet_lightbulb_skip_executing_deprioritized_analyzers", new FeatureFlagStorage(@"Roslyn.LightbulbSkipExecutingDeprioritizedAnalyzers")}, - // Do not change Lsp.PullDiagnostics. It is a shared feature flag between us and CPS. - {"dotnet_enable_pull_diagnostics", new FeatureFlagStorage(@"Lsp.PullDiagnostics")}, #pragma warning disable CS0612 // Type or member is obsolete {"dotnet_auto_xml_doc_comment_generation", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.Automatic XML Doc Comment Generation", "TextEditor.VisualBasic.Specific.AutoComment")}, #pragma warning restore @@ -350,6 +343,8 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_collapse_inline_rename_ui", new RoamingProfileStorage("TextEditor.CollapseRenameUI")}, {"dotnet_rename_use_inline_adornment", new RoamingProfileStorage("TextEditor.RenameUseInlineAdornment")}, {"dotnet_preview_inline_rename_changes", new RoamingProfileStorage("TextEditor.Specific.PreviewRename")}, + {"dotnet_collapse_suggestions_in_inline_rename_ui", new RoamingProfileStorage("TextEditor.CollapseRenameSuggestionsUI")}, + {"dotnet_rename_get_suggestions_automatically", new FeatureFlagStorage(@"Editor.AutoSmartRenameSuggestions")}, {"dotnet_rename_asynchronously", new RoamingProfileStorage("TextEditor.Specific.RenameAsynchronously")}, {"dotnet_rename_file", new RoamingProfileStorage("TextEditor.Specific.RenameFile")}, {"dotnet_rename_in_comments", new RoamingProfileStorage("TextEditor.Specific.RenameInComments")}, @@ -363,7 +358,6 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_format_on_save", new LocalUserProfileStorage(@"Roslyn\Internal\OnOff\Features", "FormatOnSave")}, {"dotnet_enable_full_solution_analysis_memory_monitor", new LocalUserProfileStorage(@"Roslyn\Internal\OnOff\Features", "Full Solution Analysis Memory Monitor")}, {"dotnet_code_analysis_in_separate_process", new LocalUserProfileStorage(@"Roslyn\Internal\OnOff\Features", "OOP64Bit")}, - {"dotnet_enable_core_clr_in_code_analysis_process", new LocalUserProfileStorage(@"Roslyn\Internal\OnOff\Features", "OOPCoreClr")}, {"dotnet_enable_server_garbage_collection_in_code_analysis_process", new FeatureFlagStorage(@"Roslyn.OOPServerGC")}, {"dotnet_remove_intellicode_recommendation_limit", new LocalUserProfileStorage(@"Roslyn\Internal\OnOff\Features", "RemoveRecommendationLimit")}, {"dotnet_enable_rename_tracking", new LocalUserProfileStorage(@"Roslyn\Internal\OnOff\Features", "Rename Tracking")}, @@ -377,7 +371,8 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_detect_and_offer_editor_features_for_probable_json_strings", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.DetectAndOfferEditorFeaturesForProbableJsonStrings")}, {"dotnet_highlight_related_json_components", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.HighlightRelatedJsonComponentsUnderCursor")}, {"dotnet_report_invalid_json_patterns", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.ReportInvalidJsonPatterns")}, - {"visual_studio_enable_key_binding_reset", new FeatureFlagStorage(@"Roslyn.KeybindingResetEnabled")}, + {"visual_studio_enable_key_binding_reset", new FeatureFlagStorage("Roslyn.KeybindingResetEnabled")}, + {"visual_studio_enable_semantic_search", new FeatureFlagStorage("Roslyn.SemanticSearchEnabled")}, {"visual_studio_key_binding_needs_reset", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "NeedsReset")}, {"visual_studio_key_binding_reset_never_show_again", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "NeverShowAgain")}, {"visual_studio_resharper_key_binding_status", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "ReSharperStatus")}, @@ -426,13 +421,14 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"visual_basic_style_unused_value_assignment_preference", new RoamingProfileStorage("TextEditor.VisualBasic.Specific.UnusedValueAssignmentPreference")}, {"visual_basic_style_unused_value_expression_statement_preference", new RoamingProfileStorage("TextEditor.VisualBasic.Specific.UnusedValueExpressionStatementPreference")}, {"visual_studio_navigate_to_object_browser", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.NavigateToObjectBrowser")}, - {"visual_studio_workspace_partial_load_mode", new FeatureFlagStorage(@"Roslyn.PartialLoadMode")}, {"dotnet_disable_recoverable_text", new FeatureFlagStorage(@"Roslyn.DisableRecoverableText")}, {"dotnet_validate_compilation_tracker_states", new FeatureFlagStorage(@"Roslyn.ValidateCompilationTrackerStates")}, {"dotnet_enable_diagnostics_in_source_generated_files", new RoamingProfileStorage("TextEditor.Roslyn.Specific.EnableDiagnosticsInSourceGeneratedFilesExperiment")}, {"dotnet_enable_diagnostics_in_source_generated_files_feature_flag", new FeatureFlagStorage(@"Roslyn.EnableDiagnosticsInSourceGeneratedFiles")}, {"dotnet_enable_opening_source_generated_files_in_workspace", new RoamingProfileStorage("TextEditor.Roslyn.Specific.EnableOpeningSourceGeneratedFilesInWorkspaceExperiment")}, {"dotnet_enable_opening_source_generated_files_in_workspace_feature_flag", new FeatureFlagStorage(@"Roslyn.SourceGeneratorsEnableOpeningInWorkspace")}, + {"dotnet_source_generator_execution", new RoamingProfileStorage("TextEditor.Roslyn.Specific.SourceGeneratorExecution")}, + {"dotnet_source_generator_execution_balanced_feature_flag", new FeatureFlagStorage(@"Roslyn.SourceGeneratorExecutionBalanced")}, {"xaml_enable_lsp_intellisense", new FeatureFlagStorage(@"Xaml.EnableLspIntelliSense")}, }; } diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersister.cs b/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersister.cs index c27d99a8f9a06..12b23cdc6f93e 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersister.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersister.cs @@ -109,7 +109,20 @@ public bool TryFetch(OptionKey2 optionKey, string storageKey, out object? value) var underlyingType = Nullable.GetUnderlyingType(storageType); if (underlyingType?.IsEnum == true) - return manager.TryGetValue(storageKey, out int? value) == GetValueResult.Success ? (value.HasValue ? Enum.ToObject(underlyingType, value.Value) : null) : default(Optional); + { + if (manager.TryGetValue(storageKey, out int? nullableValue) == GetValueResult.Success) + { + return nullableValue.HasValue ? Enum.ToObject(underlyingType, nullableValue.Value) : null; + } + else if (manager.TryGetValue(storageKey, out int value) == GetValueResult.Success) + { + return Enum.ToObject(underlyingType, value); + } + else + { + return default; + } + } if (storageType == typeof(NamingStylePreferences)) { diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index 9f22f2350777a..25767a4f03745 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -13,6 +13,14 @@ "CS"="CA719A03-D55C-48F9-85DE-D934346E7F70" "VB"="EEC3DF0D-6D3F-4544-ABF9-8E26E6A90275" +// Register Hot Reload brokered service with the debugger. +// This service is available once C# or VB project has been loaded. +[$RootKey$\Debugger\ManagedHotReload\LanguageServices\{882A9AD6-1F3B-4AC0-BABC-DD6DB41714E7}] +@="Roslyn" +"UIContexts"="CA719A03-D55C-48F9-85DE-D934346E7F70;EEC3DF0D-6D3F-4544-ABF9-8E26E6A90275" +"ServiceMoniker"="Microsoft.CodeAnalysis.LanguageServer.ManagedHotReloadLanguageService" +"ServiceVersion"="0.1" + [$RootKey$\FeatureFlags\Roslyn\LSP\Editor] "Description"="Enables the LSP-powered C#/VB editing experience outside of CodeSpaces." "Value"=dword:00000000 @@ -31,6 +39,12 @@ "Title"="Run C#/VB code analysis with ServerGC (requires restart)" "PreviewPaneChannels"="IntPreview,int.main" +[$RootKey$\FeatureFlags\Roslyn\SemanticSearchEnabled] +"Description"="Enable C# Semantic Search." +"Value"=dword:00000000 +"Title"="Enable C# Semantic Search" +"PreviewPaneChannels"="IntPreview,int.main" + // Corresponds to WellKnownExperimentNames.LspPullDiagnosticsFeatureFlag [$RootKey$\FeatureFlags\Lsp\PullDiagnostics] "Description"="Enables the LSP-powered diagnostics for managed .Net projects" diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index 637147bbaa7c4..662c15204f45d 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs @@ -15,6 +15,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; @@ -421,7 +422,7 @@ await UpdateStatusBarAsync( private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); var solutionChanged = false; ProjectId? changedProject = null; @@ -454,7 +455,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) private ValueTask ProcessWorkQueueAsync( ImmutableSegmentedList<(bool solutionChanged, ProjectId? changedProject)> workQueue, CancellationToken cancellationToken) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); Contract.ThrowIfNull(_workQueue, "How could we be processing a workqueue change without a workqueue?"); @@ -469,7 +470,7 @@ private ValueTask ProcessWorkQueueAsync( private async ValueTask ProcessWorkQueueWorkerAsync( ImmutableSegmentedList<(bool solutionChanged, ProjectId? changedProject)> workQueue, CancellationToken cancellationToken) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); await PerformNuGetProjectServiceWorkAsync(async (nugetService, cancellationToken) => { @@ -518,7 +519,7 @@ await PerformNuGetProjectServiceWorkAsync(async (nugetService, cancellat private void AddProjectsToProcess( ImmutableSegmentedList<(bool solutionChanged, ProjectId? changedProject)> workQueue, Solution solution, HashSet projectsToProcess) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); // If we detected a solution change, then we need to process all projects. // This includes all the projects that we already know about, as well as @@ -540,7 +541,7 @@ private async Task ProcessProjectChangeAsync( ProjectId projectId, CancellationToken cancellationToken) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); var project = solution.GetProject(projectId); @@ -602,14 +603,14 @@ private static async Task> GetInstalledPacka public bool IsInstalled(ProjectId projectId, string packageName) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); return _projectToInstalledPackageAndVersion.TryGetValue(projectId, out var installedPackages) && installedPackages.IsInstalled(packageName); } public ImmutableArray GetInstalledVersions(string packageName) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); using var _ = PooledHashSet.GetInstance(out var installedVersions); foreach (var state in _projectToInstalledPackageAndVersion.Values) @@ -634,7 +635,7 @@ public ImmutableArray GetInstalledVersions(string packageName) private static int CompareSplit(string[] split1, string[] split2) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); for (int i = 0, n = Math.Min(split1.Length, split2.Length); i < n; i++) { @@ -654,7 +655,7 @@ private static int CompareSplit(string[] split1, string[] split2) public ImmutableArray GetProjectsWithInstalledPackage(Solution solution, string packageName, string version) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); using var _ = ArrayBuilder.GetInstance(out var result); @@ -669,7 +670,7 @@ public ImmutableArray GetProjectsWithInstalledPackage(Solution solution } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public bool CanShowManagePackagesDialog() @@ -677,7 +678,7 @@ public bool CanShowManagePackagesDialog() private bool TryGetOrLoadNuGetPackageManager([NotNullWhen(true)] out IVsPackage? nugetPackageManager) { - this.AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (_nugetPackageManager != null) { diff --git a/src/VisualStudio/Core/Def/Preview/AbstractChange.cs b/src/VisualStudio/Core/Def/Preview/AbstractChange.cs index 1fdabc370e7bc..87e36cc118ad9 100644 --- a/src/VisualStudio/Core/Def/Preview/AbstractChange.cs +++ b/src/VisualStudio/Core/Def/Preview/AbstractChange.cs @@ -4,13 +4,11 @@ #nullable disable -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Preview; -internal abstract class AbstractChange : ForegroundThreadAffinitizedObject +internal abstract class AbstractChange { public ChangeList Children; public __PREVIEWCHANGESITEMCHECKSTATE CheckState { get; private set; } @@ -18,7 +16,6 @@ internal abstract class AbstractChange : ForegroundThreadAffinitizedObject protected PreviewEngine engine; public AbstractChange(PreviewEngine engine) - : base(engine.ThreadingContext) { this.engine = engine; if (engine.ShowCheckBoxes) diff --git a/src/VisualStudio/Core/Def/Preview/FileChange.cs b/src/VisualStudio/Core/Def/Preview/FileChange.cs index 2effe84546440..37c83672c3755 100644 --- a/src/VisualStudio/Core/Def/Preview/FileChange.cs +++ b/src/VisualStudio/Core/Def/Preview/FileChange.cs @@ -135,7 +135,7 @@ private static string GetDisplayText(string excerpt) var split = excerpt.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); if (split.Length > 1) { - return string.Format("{0} ... {1}", split[0].Trim(), split[split.Length - 1].Trim()); + return string.Format("{0} ... {1}", split[0].Trim(), split[^1].Trim()); } } diff --git a/src/VisualStudio/Core/Def/Preview/PreviewEngine.cs b/src/VisualStudio/Core/Def/Preview/PreviewEngine.cs index d07c143def77c..cc956340ad856 100644 --- a/src/VisualStudio/Core/Def/Preview/PreviewEngine.cs +++ b/src/VisualStudio/Core/Def/Preview/PreviewEngine.cs @@ -24,7 +24,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Preview; -internal class PreviewEngine : ForegroundThreadAffinitizedObject, IVsPreviewChangesEngine +internal sealed class PreviewEngine : IVsPreviewChangesEngine { private readonly IVsEditorAdaptersFactoryService _editorFactory; private readonly Solution _newSolution; @@ -43,13 +43,12 @@ internal class PreviewEngine : ForegroundThreadAffinitizedObject, IVsPreviewChan public Solution FinalSolution { get; private set; } public bool ShowCheckBoxes { get; private set; } - public PreviewEngine(IThreadingContext threadingContext, string title, string helpString, string description, string topLevelItemName, Glyph topLevelGlyph, Solution newSolution, Solution oldSolution, IComponentModel componentModel, bool showCheckBoxes = true) - : this(threadingContext, title, helpString, description, topLevelItemName, topLevelGlyph, newSolution, oldSolution, componentModel, null, showCheckBoxes) + public PreviewEngine(string title, string helpString, string description, string topLevelItemName, Glyph topLevelGlyph, Solution newSolution, Solution oldSolution, IComponentModel componentModel, bool showCheckBoxes = true) + : this(title, helpString, description, topLevelItemName, topLevelGlyph, newSolution, oldSolution, componentModel, null, showCheckBoxes) { } public PreviewEngine( - IThreadingContext threadingContext, string title, string helpString, string description, @@ -60,7 +59,6 @@ public PreviewEngine( IComponentModel componentModel, IVsImageService2 imageService, bool showCheckBoxes = true) - : base(threadingContext) { _topLevelName = topLevelItemName; _topLevelGlyph = topLevelGlyph; @@ -226,7 +224,7 @@ public void UpdatePreview(DocumentId documentId, SpanChange spanSource) // However, once they've called it once, it's always the same TextView. public void SetTextView(object textView) { - _updater ??= new PreviewUpdater(ThreadingContext, EnsureTextViewIsInitialized(textView)); + _updater ??= new PreviewUpdater(EnsureTextViewIsInitialized(textView)); } private ITextView EnsureTextViewIsInitialized(object previewTextView) diff --git a/src/VisualStudio/Core/Def/Preview/PreviewService.cs b/src/VisualStudio/Core/Def/Preview/PreviewService.cs index 84247ac889cf6..eb83716ea0794 100644 --- a/src/VisualStudio/Core/Def/Preview/PreviewService.cs +++ b/src/VisualStudio/Core/Def/Preview/PreviewService.cs @@ -18,21 +18,13 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Preview; [ExportWorkspaceServiceFactory(typeof(IPreviewDialogService), ServiceLayer.Host), Shared] -internal class PreviewDialogService : ForegroundThreadAffinitizedObject, IPreviewDialogService, IWorkspaceServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PreviewDialogService(SVsServiceProvider serviceProvider) : IPreviewDialogService, IWorkspaceServiceFactory { - private readonly IVsPreviewChangesService _previewChanges; - private readonly IComponentModel _componentModel; - private readonly IVsImageService2 _imageService; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public PreviewDialogService(IThreadingContext threadingContext, SVsServiceProvider serviceProvider) - : base(threadingContext) - { - _previewChanges = (IVsPreviewChangesService)serviceProvider.GetService(typeof(SVsPreviewChangesService)); - _componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); - _imageService = (IVsImageService2)serviceProvider.GetService(typeof(SVsImageService)); - } + private readonly IVsPreviewChangesService _previewChanges = (IVsPreviewChangesService)serviceProvider.GetService(typeof(SVsPreviewChangesService)); + private readonly IComponentModel _componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); + private readonly IVsImageService2 _imageService = (IVsImageService2)serviceProvider.GetService(typeof(SVsImageService)); public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) => this; @@ -48,7 +40,6 @@ public Solution PreviewChanges( bool showCheckBoxes = true) { var engine = new PreviewEngine( - ThreadingContext, title, helpString, description, diff --git a/src/VisualStudio/Core/Def/Preview/PreviewUpdater.cs b/src/VisualStudio/Core/Def/Preview/PreviewUpdater.cs index b5b9586e3c01b..d5f89649b35f1 100644 --- a/src/VisualStudio/Core/Def/Preview/PreviewUpdater.cs +++ b/src/VisualStudio/Core/Def/Preview/PreviewUpdater.cs @@ -15,15 +15,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Preview; -internal partial class PreviewUpdater : ForegroundThreadAffinitizedObject +internal sealed partial class PreviewUpdater { private PreviewDialogWorkspace? _previewWorkspace; private readonly ITextView _textView; private DocumentId? _currentDocumentId; private readonly PreviewTagger _tagger; - public PreviewUpdater(IThreadingContext threadingContext, ITextView textView) - : base(threadingContext) + public PreviewUpdater(ITextView textView) { _textView = textView; _tagger = new PreviewTagger(textView.TextBuffer); diff --git a/src/VisualStudio/Core/Def/Progression/GraphNavigatorExtension.cs b/src/VisualStudio/Core/Def/Progression/GraphNavigatorExtension.cs index b260427a3524f..9a51a3f72fa62 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphNavigatorExtension.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphNavigatorExtension.cs @@ -9,34 +9,26 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Navigation; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.GraphModel; using Microsoft.VisualStudio.GraphModel.CodeSchema; using Microsoft.VisualStudio.GraphModel.Schemas; -using Microsoft.CodeAnalysis.FindUsages; -using Microsoft.CodeAnalysis.GoToDefinition; -using Microsoft.CodeAnalysis.Editor.Host; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; using Workspace = Microsoft.CodeAnalysis.Workspace; -internal sealed class GraphNavigatorExtension : ForegroundThreadAffinitizedObject, IGraphNavigateToItem +internal sealed class GraphNavigatorExtension( + IThreadingContext threadingContext, + Workspace workspace, + Lazy streamingPresenter) : IGraphNavigateToItem { - private readonly Workspace _workspace; - private readonly Lazy _streamingPresenter; - - public GraphNavigatorExtension( - IThreadingContext threadingContext, - Workspace workspace, - Lazy streamingPresenter) - : base(threadingContext) - { - _workspace = workspace; - _streamingPresenter = streamingPresenter; - } + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly Workspace _workspace = workspace; + private readonly Lazy _streamingPresenter = streamingPresenter; public void NavigateTo(GraphObject graphObject) { @@ -68,7 +60,7 @@ public void NavigateTo(GraphObject graphObject) if (document == null) return; - this.ThreadingContext.JoinableTaskFactory.Run(() => + _threadingContext.JoinableTaskFactory.Run(() => NavigateToAsync(sourceLocation, symbolId, project, document, CancellationToken.None)); } } @@ -82,7 +74,7 @@ private async Task NavigateToAsync( { var symbol = symbolId.Value.Resolve(await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false), cancellationToken: cancellationToken).Symbol; await GoToDefinitionHelpers.TryNavigateToLocationAsync( - symbol, project.Solution, this.ThreadingContext, _streamingPresenter.Value, cancellationToken).ConfigureAwait(false); + symbol, project.Solution, _threadingContext, _streamingPresenter.Value, cancellationToken).ConfigureAwait(false); return; } @@ -99,7 +91,7 @@ await GoToDefinitionHelpers.TryNavigateToLocationAsync( // TODO: Get the platform to use and pass us an operation context, or create one ourselves. await navigationService.TryNavigateToLineAndOffsetAsync( - this.ThreadingContext, + _threadingContext, editorWorkspace, document.Id, sourceLocation.StartPosition.Line, diff --git a/src/VisualStudio/Core/Def/Progression/GraphNodeIdCreation.cs b/src/VisualStudio/Core/Def/Progression/GraphNodeIdCreation.cs index 7f17e6f0096a8..bb05d21142e75 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphNodeIdCreation.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphNodeIdCreation.cs @@ -198,7 +198,7 @@ private static async Task GetPartialForNamedTypeAsync(INamedTypeSym partials.Add(GraphNodeId.GetArray( CodeGraphNodeIdName.GenericArgumentsIdentifier, - genericArguments.ToArray())); + [.. genericArguments])); } if (namedType.ContainingType != null) @@ -206,7 +206,7 @@ private static async Task GetPartialForNamedTypeAsync(INamedTypeSym partials.Add(await GetPartialForTypeAsync(namedType.ContainingType, CodeGraphNodeIdName.ParentType, solution, cancellationToken, hasGenericArguments).ConfigureAwait(false)); } - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary(partials.ToArray())); + return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); } } @@ -231,7 +231,7 @@ private static async Task GetPartialForPointerTypeAsync(IPointerTyp partials.Add(await GetPartialForTypeAsync(pointerType.PointedAtType.ContainingType, CodeGraphNodeIdName.ParentType, solution, cancellationToken).ConfigureAwait(false)); } - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary(partials.ToArray())); + return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); } private static async Task GetPartialForArrayTypeAsync(IArrayTypeSymbol arrayType, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken) @@ -252,7 +252,7 @@ private static async Task GetPartialForArrayTypeAsync(IArrayTypeSym partials.Add(GraphNodeId.GetPartial(CodeQualifiedName.ArrayRank, arrayType.Rank.ToString())); partials.Add(await GetPartialForTypeAsync(arrayType.ElementType, CodeGraphNodeIdName.ParentType, solution, cancellationToken).ConfigureAwait(false)); - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary(partials.ToArray())); + return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); } private static async Task GetPartialForTypeParameterSymbolAsync(ITypeParameterSymbol typeParameterSymbol, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken) @@ -317,7 +317,7 @@ public static async Task GetIdForMemberAsync(ISymbol member, Soluti nodes.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.ParamKind, ParamKind.Ref)); } - parameterTypeIds.Add(GraphNodeId.GetNested(nodes.ToArray())); + parameterTypeIds.Add(GraphNodeId.GetNested([.. nodes])); } if (member is IMethodSymbol methodSymbol && methodSymbol.MethodKind == MethodKind.Conversion) @@ -336,25 +336,25 @@ public static async Task GetIdForMemberAsync(ISymbol member, Soluti var returnTypePartial = nodes.ToList(); returnTypePartial.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.ParamKind, Microsoft.VisualStudio.GraphModel.CodeSchema.ParamKind.Return)); - var returnCollection = GraphNodeId.GetNested(returnTypePartial.ToArray()); + var returnCollection = GraphNodeId.GetNested([.. returnTypePartial]); parameterTypeIds.Add(returnCollection); } memberPartials.Add(GraphNodeId.GetArray( CodeGraphNodeIdName.OverloadingParameters, - parameterTypeIds.ToArray())); + [.. parameterTypeIds])); } partials.Add(GraphNodeId.GetPartial( CodeGraphNodeIdName.Member, - MakeCollectionIfNecessary(memberPartials.ToArray()))); + MakeCollectionIfNecessary([.. memberPartials]))); } else { partials.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.Member, member.MetadataName)); } - return GraphNodeId.GetNested(partials.ToArray()); + return GraphNodeId.GetNested([.. partials]); } private static object MakeCollectionIfNecessary(GraphNodeId[] array) diff --git a/src/VisualStudio/Core/Def/Progression/GraphProvider.cs b/src/VisualStudio/Core/Def/Progression/GraphProvider.cs index e976e4921062f..3a9ce9b855f0b 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphProvider.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphProvider.cs @@ -69,9 +69,7 @@ private void EnsureInitialized() _initialized = true; } - public static ImmutableArray GetGraphQueries( - IGraphContext context, - IAsynchronousOperationListener asyncListener) + public static ImmutableArray GetGraphQueries(IGraphContext context) { using var _ = ArrayBuilder.GetInstance(out var graphQueries); @@ -152,19 +150,19 @@ public static ImmutableArray GetGraphQueries( // Create two queries. One to find results in normal docs, and one to find results in generated // docs. That way if the generated docs take a long time we can still report the regular doc // results immediately. - graphQueries.Add(new SearchGraphQuery(searchParameters.SearchQuery.SearchString, NavigateToSearchScope.RegularDocuments, asyncListener)); - graphQueries.Add(new SearchGraphQuery(searchParameters.SearchQuery.SearchString, NavigateToSearchScope.GeneratedDocuments, asyncListener)); + graphQueries.Add(new SearchGraphQuery(searchParameters.SearchQuery.SearchString, NavigateToDocumentSupport.RegularDocuments)); + graphQueries.Add(new SearchGraphQuery(searchParameters.SearchQuery.SearchString, NavigateToDocumentSupport.GeneratedDocuments)); } } - return graphQueries.ToImmutable(); + return graphQueries.ToImmutableAndClear(); } public void BeginGetGraphData(IGraphContext context) { EnsureInitialized(); - var graphQueries = GetGraphQueries(context, _asyncListener); + var graphQueries = GetGraphQueries(context); // Perform the queries asynchronously in a fire-and-forget fashion. This helper will be responsible // for always completing the context. AddQueriesAsync is `async`, so it always returns a task and will never diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/CallsGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/CallsGraphQuery.cs index 9ccf6ac974df6..d4c269556a082 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/CallsGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphQueries/CallsGraphQuery.cs @@ -60,6 +60,6 @@ private static async Task> GetCalledMethodSymbolsAsync( } } - return symbols.ToImmutable(); + return symbols.ToImmutableAndClear(); } } diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs index 5ad659283582a..1c2da80987c39 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.GraphModel; using Microsoft.CodeAnalysis.NavigateTo; +using System.Collections.Immutable; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; @@ -14,11 +15,13 @@ internal sealed partial class SearchGraphQuery { private class ProgressionNavigateToSearchCallback : INavigateToSearchCallback { + private readonly Solution _solution; private readonly IGraphContext _context; private readonly GraphBuilder _graphBuilder; - public ProgressionNavigateToSearchCallback(IGraphContext context, GraphBuilder graphBuilder) + public ProgressionNavigateToSearchCallback(Solution solution, IGraphContext context, GraphBuilder graphBuilder) { + _solution = solution; _context = context; _graphBuilder = graphBuilder; } @@ -36,14 +39,17 @@ public void ReportIncomplete() { } - public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - var node = await _graphBuilder.CreateNodeAsync(project.Solution, result, cancellationToken).ConfigureAwait(false); - if (node != null) + foreach (var result in results) { - // _context.OutputNodes is not threadsafe. So ensure only one navto callback can mutate it at a time. - lock (this) - _context.OutputNodes.Add(node); + var node = await _graphBuilder.CreateNodeAsync(_solution, result, cancellationToken).ConfigureAwait(false); + if (node != null) + { + // _context.OutputNodes is not threadsafe. So ensure only one navto callback can mutate it at a time. + lock (this) + _context.OutputNodes.Add(node); + } } } } diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs index c1b1dd36c5898..2c95f2f1c2315 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs @@ -8,20 +8,18 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.GraphModel; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; internal sealed partial class SearchGraphQuery( string searchPattern, - NavigateToSearchScope searchScope, - IAsynchronousOperationListener asyncListener) : IGraphQuery + NavigateToDocumentSupport searchScope) : IGraphQuery { public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) { var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - var callback = new ProgressionNavigateToSearchCallback(context, graphBuilder); + var callback = new ProgressionNavigateToSearchCallback(solution, context, graphBuilder); // We have a specialized host for progression vs normal nav-to. Progression itself will tell the client if // the project is fully loaded or not. But after that point, the client will be considered fully loaded and @@ -33,13 +31,12 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c var searcher = NavigateToSearcher.Create( solution, - asyncListener, callback, searchPattern, NavigateToUtilities.GetKindsProvided(solution), host); - await searcher.SearchAsync(searchCurrentDocument: false, searchScope, cancellationToken).ConfigureAwait(false); + await searcher.SearchAsync(NavigateToSearchScope.Solution, searchScope, cancellationToken).ConfigureAwait(false); return graphBuilder; } diff --git a/src/VisualStudio/Core/Def/Progression/SymbolContainment.cs b/src/VisualStudio/Core/Def/Progression/SymbolContainment.cs index a94b1d6503d2f..0b298e0553561 100644 --- a/src/VisualStudio/Core/Def/Progression/SymbolContainment.cs +++ b/src/VisualStudio/Core/Def/Progression/SymbolContainment.cs @@ -26,9 +26,7 @@ public static async Task> GetContainedSyntaxNodesAsync(D { var progressionLanguageService = document.GetLanguageService(); if (progressionLanguageService == null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var root = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); @@ -54,7 +52,7 @@ public static async Task> GetContainedSymbolsAsync(Docum } } - return symbols.ToImmutable(); + return symbols.ToImmutableAndClear(); } private static bool IsTopLevelSymbol(ISymbol symbol) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Extensions/ProjectExtensions.cs b/src/VisualStudio/Core/Def/ProjectSystem/Extensions/ProjectExtensions.cs index f4c74c21c6c56..776ecc7c91ee6 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Extensions/ProjectExtensions.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Extensions/ProjectExtensions.cs @@ -8,6 +8,8 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using EnvDTE; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Extensions; @@ -52,13 +54,11 @@ private static ProjectItem CreateFolder(ProjectItems currentItems, string contai public static ProjectItem? FindItemByPath(this EnvDTE.Project project, string itemFilePath, StringComparer comparer) { - var stack = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(project.ProjectItems); - while (stack.Count > 0) + while (stack.TryPop(out var currentItems)) { - var currentItems = stack.Pop(); - foreach (var projectItem in currentItems.OfType()) { if (projectItem.TryGetFullPath(out var filePath) && comparer.Equals(filePath, itemFilePath)) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/InvisibleEditor.cs b/src/VisualStudio/Core/Def/ProjectSystem/InvisibleEditor.cs index 551e6e4202f51..f2c0d304e6bd0 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/InvisibleEditor.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/InvisibleEditor.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.VisualStudio.Editor; @@ -17,7 +18,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; -internal partial class InvisibleEditor : ForegroundThreadAffinitizedObject, IInvisibleEditor +internal sealed partial class InvisibleEditor : IInvisibleEditor { private readonly IServiceProvider _serviceProvider; private readonly string _filePath; @@ -31,6 +32,7 @@ internal partial class InvisibleEditor : ForegroundThreadAffinitizedObject, IInv private IVsInvisibleEditor _invisibleEditor; private OLE.Interop.IOleUndoManager? _manager; private readonly bool _needsUndoRestored; + private readonly IThreadingContext _threadingContext; /// /// The optional project is used to obtain an instance. When this instance is @@ -40,8 +42,9 @@ internal partial class InvisibleEditor : ForegroundThreadAffinitizedObject, IInv /// projects in the solution. /// public InvisibleEditor(IServiceProvider serviceProvider, string filePath, IVsHierarchy? hierarchy, bool needsSave, bool needsUndoDisabled) - : base(serviceProvider.GetMefService(), assertIsForeground: true) { + _threadingContext = serviceProvider.GetMefService(); + _threadingContext.ThrowIfNotOnUIThread(); _serviceProvider = serviceProvider; _filePath = filePath; _needsSave = needsSave; @@ -143,7 +146,7 @@ public ITextBuffer TextBuffer /// public void Dispose() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); _buffer = null; _vsTextLines = null!; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs index 707aae6f45478..fab4757f37e23 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.ComponentModelHost; @@ -27,7 +28,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.L /// Base type for legacy C# and VB project system shim implementations. /// These legacy shims are based on legacy project system interfaces defined in csproj/msvbprj. /// -internal abstract partial class AbstractLegacyProject : ForegroundThreadAffinitizedObject +internal abstract partial class AbstractLegacyProject { public IVsHierarchy Hierarchy { get; } protected ProjectSystemProject ProjectSystemProject { get; } @@ -65,8 +66,9 @@ public AbstractLegacyProject( IServiceProvider serviceProvider, IThreadingContext threadingContext, string externalErrorReportingPrefix) - : base(threadingContext, assertIsForeground: true) { + _threadingContext = threadingContext; + _threadingContext.ThrowIfNotOnUIThread(); Contract.ThrowIfNull(hierarchy); var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); @@ -173,7 +175,7 @@ protected void AddFile( string filename, SourceCodeKind sourceCodeKind) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); // We have tests that assert that XOML files should not get added; this was similar // behavior to how ASP.NET projects would add .aspx files even though we ultimately ignored @@ -302,6 +304,7 @@ private static Guid GetProjectIDGuid(IVsHierarchy hierarchy) /// Using item IDs as a key like this in a long-lived way is considered unsupported by CPS and other /// IVsHierarchy providers, but this code (which is fairly old) still makes the assumptions anyways. private readonly Dictionary> _folderNameMap = []; + private readonly IThreadingContext _threadingContext; private ImmutableArray GetFolderNamesForDocument(string filename) { @@ -316,7 +319,7 @@ private ImmutableArray GetFolderNamesForDocument(string filename) private ImmutableArray GetFolderNamesForDocument(uint documentItemID) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (documentItemID != (uint)VSConstants.VSITEMID.Nil && Hierarchy.GetProperty(documentItemID, (int)VsHierarchyPropID.Parent, out var parentObj) == VSConstants.S_OK) { @@ -332,7 +335,7 @@ private ImmutableArray GetFolderNamesForDocument(uint documentItemID) private ImmutableArray GetFolderNamesForFolder(uint folderItemID) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); using var pooledObject = SharedPools.Default>().GetPooledObject(); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs index 21690e8663ef5..9a78bcbae6dbf 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; @@ -23,28 +24,22 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.L /// /// All members of this class are affinitized to the UI thread. [Export(typeof(SolutionEventsBatchScopeCreator))] -internal sealed class SolutionEventsBatchScopeCreator : ForegroundThreadAffinitizedObject +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class SolutionEventsBatchScopeCreator(IThreadingContext threadingContext, [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) { private readonly List<(ProjectSystemProject project, IVsHierarchy hierarchy, ProjectSystemProject.BatchScope batchScope)> _fullSolutionLoadScopes = new List<(ProjectSystemProject, IVsHierarchy, ProjectSystemProject.BatchScope)>(); - private uint? _runningDocumentTableEventsCookie; - - private readonly IServiceProvider _serviceProvider; + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly IServiceProvider _serviceProvider = serviceProvider; + private uint? _runningDocumentTableEventsCookie; private bool _isSubscribedToSolutionEvents = false; private bool _solutionLoaded = false; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SolutionEventsBatchScopeCreator(IThreadingContext threadingContext, [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) - : base(threadingContext, assertIsForeground: false) - { - _serviceProvider = serviceProvider; - } - public void StartTrackingProject(ProjectSystemProject project, IVsHierarchy hierarchy) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); EnsureSubscribedToSolutionEvents(); @@ -58,7 +53,7 @@ public void StartTrackingProject(ProjectSystemProject project, IVsHierarchy hier public void StopTrackingProject(ProjectSystemProject project) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); foreach (var scope in _fullSolutionLoadScopes) { @@ -75,7 +70,7 @@ public void StopTrackingProject(ProjectSystemProject project) private void StopTrackingAllProjects() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); foreach (var (_, _, batchScope) in _fullSolutionLoadScopes) { @@ -89,7 +84,7 @@ private void StopTrackingAllProjects() private void StopTrackingAllProjectsMatchingHierarchy(IVsHierarchy hierarchy) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); for (var i = 0; i < _fullSolutionLoadScopes.Count; i++) { @@ -108,7 +103,7 @@ private void StopTrackingAllProjectsMatchingHierarchy(IVsHierarchy hierarchy) private void EnsureSubscribedToSolutionEvents() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_isSubscribedToSolutionEvents) { @@ -139,7 +134,7 @@ private void EnsureSubscribedToSolutionEvents() private void EnsureSubscribedToRunningDocumentTableEvents() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_runningDocumentTableEventsCookie.HasValue) { @@ -156,7 +151,7 @@ private void EnsureSubscribedToRunningDocumentTableEvents() private void EnsureUnsubscribedFromRunningDocumentTableEventsIfNoLongerNeeded() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (!_runningDocumentTableEventsCookie.HasValue) { diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioFrameworkAssemblyPathResolverFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioFrameworkAssemblyPathResolverFactory.cs index 010223482f916..06e29fc248375 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioFrameworkAssemblyPathResolverFactory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioFrameworkAssemblyPathResolverFactory.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.Versioning; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; @@ -34,24 +35,18 @@ public VisualStudioFrameworkAssemblyPathResolverFactory(IThreadingContext thread public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) => new Service(_threadingContext, workspaceServices.Workspace as VisualStudioWorkspace, _serviceProvider); - private sealed class Service : ForegroundThreadAffinitizedObject, IFrameworkAssemblyPathResolver + private sealed class Service(IThreadingContext threadingContext, VisualStudioWorkspace? workspace, IServiceProvider serviceProvider) : IFrameworkAssemblyPathResolver { - private readonly VisualStudioWorkspace? _workspace; - private readonly IServiceProvider _serviceProvider; - - public Service(IThreadingContext threadingContext, VisualStudioWorkspace? workspace, IServiceProvider serviceProvider) - : base(threadingContext, assertIsForeground: false) - { - _workspace = workspace; - _serviceProvider = serviceProvider; - } + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly VisualStudioWorkspace? _workspace = workspace; + private readonly IServiceProvider _serviceProvider = serviceProvider; public string? ResolveAssemblyPath( ProjectId projectId, string assemblyName, string? fullyQualifiedTypeName) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var assembly = ResolveAssembly(projectId, assemblyName); if (assembly != null) @@ -111,7 +106,7 @@ private static bool CanResolveType(Assembly assembly, string? fullyQualifiedType private Assembly? ResolveAssembly(ProjectId projectId, string assemblyName) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_workspace == null) { diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 8b8d1b5f0cc5f..761a41ea3d178 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList GetStorages() - => _provider.GetStorages(this.FilePath, _timestamp.Value); + public IReadOnlyList StorageHandles + => _provider.GetStorageHandles(this.FilePath, _timestamp.Value); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 8fe74c01b94e9..01a6a41f6695d 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -22,6 +22,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; +using static TemporaryStorageService; + /// /// Manages metadata references for VS projects. /// @@ -37,13 +39,13 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS private static readonly ConditionalWeakTable s_lifetimeMap = new(); /// - /// Mapping from an we created, to the memory mapped files (mmf) corresponding to - /// the assembly and all the modules within it. This is kept around to make OOP syncing more efficient. - /// Specifically, since we know we read the assembly into an mmf, we can just send the mmf name/offset/length to - /// the remote process, and it can map that same memory in directly, instead of needing the host to send the - /// entire contents of the assembly over the channel to the OOP process. + /// Mapping from an we created, to the identifiers identifying the memory mapped + /// files (mmf) corresponding to that assembly and all the modules within it. This is kept around to make OOP + /// syncing more efficient. Specifically, since we know we dumped the assembly into an mmf, we can just send the mmf + /// name/offset/length to the remote process, and it can map that same memory in directly, instead of needing the + /// host to send the entire contents of the assembly over the channel to the OOP process. /// - private static readonly ConditionalWeakTable> s_metadataToStorages = new(); + private static readonly ConditionalWeakTable> s_metadataToStorageHandles = new(); private readonly MetadataCache _metadataCache = new(); private readonly ImmutableArray _runtimeDirectories; @@ -86,14 +88,14 @@ public void Dispose() } } - public IReadOnlyList? GetStorages(string fullPath, DateTime snapshotTimestamp) + public IReadOnlyList? GetStorageHandles(string fullPath, DateTime snapshotTimestamp) { var key = new FileKey(fullPath, snapshotTimestamp); // check existing metadata if (_metadataCache.TryGetMetadata(key, out var source) && - s_metadataToStorages.TryGetValue(source, out var storages)) + s_metadataToStorageHandles.TryGetValue(source, out var handles)) { - return storages; + return handles; } return null; @@ -154,45 +156,35 @@ AssemblyMetadata GetMetadataWorker() else { // use temporary storage - using var _ = ArrayBuilder.GetInstance(out var storages); - var newMetadata = CreateAssemblyMetadata(key, key => - { - // - // - GetMetadataFromTemporaryStorage(key, out var storage, out var metadata); - storages.Add(storage); - return metadata; - }); - - var storagesArray = storages.ToImmutable(); - - s_metadataToStorages.Add(newMetadata, storagesArray); - - return newMetadata; + return CreateAssemblyMetadata(key, GetMetadataFromTemporaryStorage); } } } - private void GetMetadataFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageService.TemporaryStreamStorage storage, out ModuleMetadata metadata) + private (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) GetMetadataFromTemporaryStorage( + FileKey moduleFileKey) { - GetStorageInfoFromTemporaryStorage(moduleFileKey, out storage, out var stream); + GetStorageInfoFromTemporaryStorage(moduleFileKey, out var storageHandle, out var stream); unsafe { - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. - metadata = ModuleMetadata.CreateFromMetadata((IntPtr)stream.PositionPointer, (int)stream.Length, stream.Dispose); + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Passing in stream.Dispose + // here will also ensure that as long as this metdata is alive, we'll keep the memory-mapped-file it points + // to alive. + var metadata = ModuleMetadata.CreateFromMetadata((IntPtr)stream.PositionPointer, (int)stream.Length, stream.Dispose); + return (metadata, storageHandle); } - return; - void GetStorageInfoFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageService.TemporaryStreamStorage storage, out UnmanagedMemoryStream stream) + FileKey moduleFileKey, out TemporaryStorageStreamHandle storageHandle, out UnmanagedMemoryStream stream) { int size; + + // Create a temp stream in memory to copy the metadata bytes into. using (var copyStream = SerializableBytes.CreateWritableStream()) { - // open a file and let it go as soon as possible + // Open a file on disk, find the metadata section, copy those bytes into the temp stream, and release + // the file immediately after. using (var fileStream = FileUtilities.OpenRead(moduleFileKey.FullPath)) { var headers = new PEHeaders(fileStream); @@ -210,16 +202,15 @@ void GetStorageInfoFromTemporaryStorage( StreamCopy(fileStream, copyStream, offset, size); } - // copy over the data to temp storage and let pooled stream go - storage = _temporaryStorageService.CreateTemporaryStreamStorage(); - + // Now, copy over the metadata bytes into a memory mapped file. This will keep it fixed in a single + // location, so we can create a metadata value wrapping that. This will also let us share the memory + // for that metadata value with our OOP process. copyStream.Position = 0; - storage.WriteStream(copyStream); + storageHandle = _temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); } - // get stream that owns the underlying unmanaged memory. - stream = storage.ReadStream(CancellationToken.None); - + // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. + stream = storageHandle.ReadFromTemporaryStorage(CancellationToken.None); // stream size must be same as what metadata reader said the size should be. Contract.ThrowIfFalse(stream.Length == size); } @@ -249,12 +240,11 @@ private AssemblyMetadata CreateAssemblyMetadataFromMetadataImporter(FileKey file return CreateAssemblyMetadata(fileKey, fileKey => { var metadata = TryCreateModuleMetadataFromMetadataImporter(fileKey); + if (metadata != null) + return (metadata, storageHandle: null); // getting metadata didn't work out through importer. fallback to shadow copy one - if (metadata == null) - GetMetadataFromTemporaryStorage(fileKey, out _, out metadata); - - return metadata; + return GetMetadataFromTemporaryStorage(fileKey); }); ModuleMetadata? TryCreateModuleMetadataFromMetadataImporter(FileKey moduleFileKey) @@ -311,11 +301,12 @@ bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)] /// private static AssemblyMetadata CreateAssemblyMetadata( FileKey fileKey, - Func moduleMetadataFactory) + Func moduleMetadataFactory) { - var manifestModule = moduleMetadataFactory(fileKey); + var (manifestModule, manifestHandle) = moduleMetadataFactory(fileKey); - using var _ = ArrayBuilder.GetInstance(out var moduleBuilder); + using var _1 = ArrayBuilder.GetInstance(out var moduleBuilder); + using var _2 = ArrayBuilder.GetInstance(out var storageHandles); string? assemblyDir = null; foreach (var moduleName in manifestModule.GetModuleNames()) @@ -328,14 +319,27 @@ private static AssemblyMetadata CreateAssemblyMetadata( // Suppression should be removed or addressed https://github.com/dotnet/roslyn/issues/41636 var moduleFileKey = FileKey.Create(PathUtilities.CombineAbsoluteAndRelativePaths(assemblyDir, moduleName)!); - var metadata = moduleMetadataFactory(moduleFileKey); + var (metadata, metadataStorageHandle) = moduleMetadataFactory(moduleFileKey); moduleBuilder.Add(metadata); + storageHandles.Add(metadataStorageHandle); } if (moduleBuilder.Count == 0) + { moduleBuilder.Add(manifestModule); + storageHandles.Add(manifestHandle); + } + + var result = AssemblyMetadata.Create(moduleBuilder.ToImmutable()); + + // If we got any null handles, then we weren't able to map this whole assembly into memory mapped files. So we + // can't use those to transfer over the data efficiently to the OOP process. In that case, we don't store the + // handles at all. + Contract.ThrowIfTrue(storageHandles.Count == 0); + if (storageHandles.All(h => h != null)) + s_metadataToStorageHandles.Add(result, storageHandles.ToImmutable()); - return AssemblyMetadata.Create(moduleBuilder.ToImmutable()); + return result; } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs b/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs index 63948c018ba84..ace82412c8ab2 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs @@ -12,6 +12,8 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Features.Workspaces; using Microsoft.CodeAnalysis.Host.Mef; @@ -29,6 +31,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; [Export(typeof(MiscellaneousFilesWorkspace))] internal sealed partial class MiscellaneousFilesWorkspace : Workspace, IOpenTextBufferEventListener { + private readonly IThreadingContext _threadingContext; private readonly IVsService _textManagerService; private readonly OpenTextBufferProvider _openTextBufferProvider; private readonly IMetadataAsSourceFileService _fileTrackingMetadataAsSourceService; @@ -49,8 +52,6 @@ internal sealed partial class MiscellaneousFilesWorkspace : Workspace, IOpenText private readonly ImmutableArray _metadataReferences; - private readonly ForegroundThreadAffinitizedObject _foregroundThreadAffinitization; - private IVsTextManager _textManager; [ImportingConstructor] @@ -63,8 +64,7 @@ public MiscellaneousFilesWorkspace( VisualStudioWorkspace visualStudioWorkspace) : base(visualStudioWorkspace.Services.HostServices, WorkspaceKind.MiscellaneousFiles) { - _foregroundThreadAffinitization = new ForegroundThreadAffinitizedObject(threadingContext, assertIsForeground: false); - + _threadingContext = threadingContext; _textManagerService = textManagerService; _openTextBufferProvider = openTextBufferProvider; _fileTrackingMetadataAsSourceService = fileTrackingMetadataAsSourceService; @@ -134,7 +134,7 @@ private IEnumerable CreateMetadataReferences() private void TrackOpenedDocument(string moniker, ITextBuffer textBuffer) { - _foregroundThreadAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var languageInformation = TryGetLanguageInformation(moniker); if (languageInformation == null) @@ -164,13 +164,13 @@ private void Registration_WorkspaceChanged(object sender, EventArgs e) // We may or may not be getting this notification from the foreground thread if another workspace // is raising events on a background. Let's send it back to the UI thread since we can't talk // to the RDT in the background thread. Since this is all asynchronous a bit more asynchrony is fine. - if (!_foregroundThreadAffinitization.IsForeground()) + if (!_threadingContext.JoinableTaskContext.IsOnMainThread) { ScheduleTask(() => Registration_WorkspaceChanged(sender, e)); return; } - _foregroundThreadAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var workspaceRegistration = (WorkspaceRegistration)sender; @@ -229,7 +229,7 @@ private void Registration_WorkspaceChanged(object sender, EventArgs e) /// true if we were previously tracking it. private bool TryUntrackClosingDocument(string moniker) { - _foregroundThreadAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var unregisteredRegistration = false; @@ -257,7 +257,7 @@ private static bool IsClaimedByAnotherWorkspace(WorkspaceRegistration registrati private void AttachToDocument(string moniker, ITextBuffer textBuffer) { - _foregroundThreadAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_fileTrackingMetadataAsSourceService.TryAddDocumentToWorkspace(moniker, textBuffer.AsTextContainer())) { @@ -292,7 +292,7 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) private void DetachFromDocument(string moniker) { - _foregroundThreadAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_fileTrackingMetadataAsSourceService.TryRemoveDocumentFromWorkspace(moniker)) { return; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/OpenTextBufferProvider.cs b/src/VisualStudio/Core/Def/ProjectSystem/OpenTextBufferProvider.cs index 3d2d266a38982..56e42b07e4644 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/OpenTextBufferProvider.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/OpenTextBufferProvider.cs @@ -8,6 +8,7 @@ using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; @@ -33,11 +34,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; internal sealed class OpenTextBufferProvider : IVsRunningDocTableEvents3, IDisposable { private bool _isDisposed = false; + private readonly IThreadingContext _threadingContext; /// /// A simple object for asserting when we're on the UI thread. /// - private readonly ForegroundThreadAffinitizedObject _foregroundAffinitization; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; private readonly IVsRunningDocumentTable4 _runningDocumentTable; @@ -58,8 +59,7 @@ public OpenTextBufferProvider( [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, IAsynchronousOperationListenerProvider listenerProvider) { - _foregroundAffinitization = new ForegroundThreadAffinitizedObject(threadingContext, assertIsForeground: false); - + _threadingContext = threadingContext; _editorAdaptersFactoryService = editorAdaptersFactoryService; // The running document table since 16.0 has limited operations that can be done in a free threaded manner, specifically fetching the service and advising events. @@ -77,7 +77,7 @@ public OpenTextBufferProvider( private void RaiseEventForEachListener(Action action) { - _foregroundAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); foreach (var listener in _listeners) { @@ -119,7 +119,7 @@ public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint d { if (dwReadLocksRemaining + dwEditLocksRemaining == 0) { - _foregroundAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_runningDocumentTable.IsDocumentInitialized(docCookie)) { var moniker = _runningDocumentTable.GetDocumentMoniker(docCookie); @@ -143,7 +143,7 @@ public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarch // Did we rename? if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_MkDocument) != 0) { - _foregroundAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_runningDocumentTable.IsDocumentInitialized(docCookie)) { // We should already have a text buffer for this one @@ -174,7 +174,7 @@ public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarch // but that might still not be associated with an ITextBuffer. if ((grfAttribs & ((uint)__VSRDTATTRIB.RDTA_DocDataReloaded | (uint)__VSRDTATTRIB3.RDTA_DocumentInitialized)) != 0) { - _foregroundAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_runningDocumentTable.IsDocumentInitialized(docCookie) && TryGetMoniker(docCookie, out var moniker) && TryGetBufferFromRunningDocumentTable(docCookie, out var buffer)) { _monikerToTextBufferMap = _monikerToTextBufferMap.Add(moniker, buffer); @@ -186,7 +186,7 @@ public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarch if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_Hierarchy) != 0) { - _foregroundAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_runningDocumentTable.IsDocumentInitialized(docCookie) && TryGetMoniker(docCookie, out var moniker)) { _runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out _); @@ -248,7 +248,7 @@ public bool IsFileOpen(string filePath) /// public IVsHierarchy? GetDocumentHierarchy(string filePath) { - _foregroundAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (!_runningDocumentTable.IsFileOpen(filePath)) { @@ -265,7 +265,7 @@ public bool IsFileOpen(string filePath) /// public IEnumerable<(string filePath, ITextBuffer textBuffer, IVsHierarchy hierarchy)> EnumerateDocumentSet() { - _foregroundAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var documents = ArrayBuilder<(string, ITextBuffer, IVsHierarchy)>.GetInstance(); foreach (var cookie in GetInitializedRunningDocumentTableCookies()) @@ -299,7 +299,7 @@ private bool TryGetMoniker(uint docCookie, out string moniker) private bool TryGetBufferFromRunningDocumentTable(uint docCookie, [NotNullWhen(true)] out ITextBuffer? textBuffer) { - _foregroundAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); return _runningDocumentTable.TryGetBuffer(_editorAdaptersFactoryService, docCookie, out textBuffer); } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs index 888fdd8541c64..77f1ef30a6e9f 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs @@ -33,7 +33,6 @@ internal sealed class VisualStudioProjectFactory : IVsTypeScriptVisualStudioProj private readonly IThreadingContext _threadingContext; private readonly VisualStudioWorkspaceImpl _visualStudioWorkspaceImpl; private readonly ImmutableArray> _dynamicFileInfoProviders; - private readonly HostDiagnosticUpdateSource _hostDiagnosticUpdateSource; private readonly IVisualStudioDiagnosticAnalyzerProviderFactory _vsixAnalyzerProviderFactory; private readonly IVsService _solution2; @@ -43,14 +42,12 @@ public VisualStudioProjectFactory( IThreadingContext threadingContext, VisualStudioWorkspaceImpl visualStudioWorkspaceImpl, [ImportMany] IEnumerable> fileInfoProviders, - HostDiagnosticUpdateSource hostDiagnosticUpdateSource, IVisualStudioDiagnosticAnalyzerProviderFactory vsixAnalyzerProviderFactory, IVsService solution2) { _threadingContext = threadingContext; _visualStudioWorkspaceImpl = visualStudioWorkspaceImpl; _dynamicFileInfoProviders = fileInfoProviders.AsImmutableOrEmpty(); - _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; _vsixAnalyzerProviderFactory = vsixAnalyzerProviderFactory; _solution2 = solution2; } @@ -68,6 +65,7 @@ public async Task CreateAndAddToWorkspaceAsync( _visualStudioWorkspaceImpl.Services.GetRequiredService(); _visualStudioWorkspaceImpl.SubscribeExternalErrorDiagnosticUpdateSourceToSolutionBuildEvents(); + _visualStudioWorkspaceImpl.SubscribeToSourceGeneratorImpactingEvents(); #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task // Since we're on the UI thread here anyways, use that as an opportunity to grab the @@ -96,7 +94,7 @@ public async Task CreateAndAddToWorkspaceAsync( _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.SolutionPath = solutionFilePath; _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.SolutionTelemetryId = GetSolutionSessionId(); - var hostInfo = new ProjectSystemHostInfo(_dynamicFileInfoProviders, _hostDiagnosticUpdateSource, vsixAnalyzerProvider); + var hostInfo = new ProjectSystemHostInfo(_dynamicFileInfoProviders, HostDiagnosticUpdateSource.Instance, vsixAnalyzerProvider); var project = await _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.CreateAndAddToWorkspaceAsync(projectSystemName, language, creationInfo, hostInfo); _visualStudioWorkspaceImpl.AddProjectToInternalMaps(project, creationInfo.Hierarchy, creationInfo.ProjectGuid, projectSystemName); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs index 8f873e11e1c15..20f674aa70bab 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs @@ -10,8 +10,10 @@ using System.Linq; using EnvDTE; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.ProjectManagement; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Extensions; @@ -20,18 +22,15 @@ namespace Roslyn.VisualStudio.Services.Implementation.ProjectSystem; [ExportWorkspaceService(typeof(IProjectManagementService), ServiceLayer.Host), Shared] -internal class VisualStudioProjectManagementService : ForegroundThreadAffinitizedObject, IProjectManagementService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class VisualStudioProjectManagementService(IThreadingContext threadingContext) : IProjectManagementService { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioProjectManagementService(IThreadingContext threadingContext) - : base(threadingContext) - { - } + private readonly IThreadingContext _threadingContext = threadingContext; public string GetDefaultNamespace(Microsoft.CodeAnalysis.Project project, Workspace workspace) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (project.Language == LanguageNames.VisualBasic) { @@ -69,13 +68,12 @@ public IList GetFolders(ProjectId projectId, Workspace workspace) var projectItems = envDTEProject.ProjectItems; - var projectItemsStack = new Stack>(); + using var _ = ArrayBuilder>.GetInstance(out var projectItemsStack); // Populate the stack projectItems.OfType().Where(n => n.IsFolder()).Do(n => projectItemsStack.Push(Tuple.Create(n, "\\"))); - while (projectItemsStack.Count != 0) + while (projectItemsStack.TryPop(out var projectItemTuple)) { - var projectItemTuple = projectItemsStack.Pop(); var projectItem = projectItemTuple.Item1; var currentFolderPath = projectItemTuple.Item2; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs index a2881ef13ee65..a011645396ef9 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -35,8 +36,6 @@ internal partial class VisualStudioWorkspaceImpl /// public sealed class OpenFileTracker : IOpenTextBufferEventListener { - private readonly ForegroundThreadAffinitizedObject _foregroundAffinitization; - private readonly VisualStudioWorkspaceImpl _workspace; private readonly ProjectSystemProjectFactory _projectSystemProjectFactory; private readonly IEditorOptionsFactoryService _editorOptionsFactoryService; @@ -62,9 +61,9 @@ public sealed class OpenFileTracker : IOpenTextBufferEventListener private OpenFileTracker(VisualStudioWorkspaceImpl workspace, ProjectSystemProjectFactory projectSystemProjectFactory, IComponentModel componentModel) { + workspace._threadingContext.ThrowIfNotOnUIThread(); _workspace = workspace; _projectSystemProjectFactory = projectSystemProjectFactory; - _foregroundAffinitization = new ForegroundThreadAffinitizedObject(workspace._threadingContext, assertIsForeground: true); _editorOptionsFactoryService = componentModel.GetService(); _asynchronousOperationListener = componentModel.GetService().GetListener(FeatureAttribute.Workspace); _openTextBufferProvider = componentModel.GetService(); @@ -98,7 +97,7 @@ public static async Task CreateAsync(VisualStudioWorkspaceImpl private void TryOpeningDocumentsForMonikerAndSetContextOnUIThread(string moniker, ITextBuffer textBuffer, IVsHierarchy? hierarchy) { - _foregroundAffinitization.AssertIsForeground(); + _workspace._threadingContext.ThrowIfNotOnUIThread(); _projectSystemProjectFactory.ApplyChangeToWorkspace(w => { @@ -111,7 +110,7 @@ private void TryOpeningDocumentsForMonikerAndSetContextOnUIThread(string moniker private void EnsureSuggestedActionsSourceProviderEnabled() { - _foregroundAffinitization.AssertIsForeground(); + _workspace._threadingContext.ThrowIfNotOnUIThread(); if (!_anyDocumentOpened) { @@ -133,7 +132,7 @@ private bool TryOpeningDocumentsForFilePathCore(Workspace workspace, string moni { // If this method is given a hierarchy, we will need to be on the UI thread to use it; in any other case, we can be free-threaded. if (hierarchy != null) - _foregroundAffinitization.AssertIsForeground(); + _workspace._threadingContext.ThrowIfNotOnUIThread(); var documentIds = _projectSystemProjectFactory.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(moniker); if (documentIds.IsDefaultOrEmpty) @@ -189,7 +188,7 @@ private bool TryOpeningDocumentsForFilePathCore(Workspace workspace, string moni private ProjectId GetActiveContextProjectIdAndWatchHierarchies_NoLock(string moniker, IEnumerable projectIds, IVsHierarchy? hierarchy) { - _foregroundAffinitization.AssertIsForeground(); + _workspace._threadingContext.ThrowIfNotOnUIThread(); // First clear off any existing IVsHierarchies we are watching. Any ones that still matter we will resubscribe to. // We could be fancy and diff, but the cost is probably negligible. @@ -260,7 +259,7 @@ void WatchHierarchy(IVsHierarchy hierarchyToWatch) private void UnsubscribeFromWatchedHierarchies(string moniker) { - _foregroundAffinitization.AssertIsForeground(); + _workspace._threadingContext.ThrowIfNotOnUIThread(); foreach (var watchedHierarchy in _watchedHierarchiesForDocumentMoniker[moniker]) { @@ -272,7 +271,7 @@ private void UnsubscribeFromWatchedHierarchies(string moniker) private void RefreshContextForMoniker(string moniker, IVsHierarchy hierarchy) { - _foregroundAffinitization.AssertIsForeground(); + _workspace._threadingContext.ThrowIfNotOnUIThread(); _projectSystemProjectFactory.ApplyChangeToWorkspace(w => { @@ -294,7 +293,7 @@ private void RefreshContextForMoniker(string moniker, IVsHierarchy hierarchy) private void RefreshContextsForHierarchyPropertyChange(IVsHierarchy hierarchy) { - _foregroundAffinitization.AssertIsForeground(); + _workspace._threadingContext.ThrowIfNotOnUIThread(); // We're going to go through each file that has subscriptions, and update them appropriately. // We have to clone this since we will be modifying it under the covers. @@ -312,7 +311,7 @@ private void RefreshContextsForHierarchyPropertyChange(IVsHierarchy hierarchy) private void TryClosingDocumentsForMoniker(string moniker) { - _foregroundAffinitization.AssertIsForeground(); + _workspace._threadingContext.ThrowIfNotOnUIThread(); UnsubscribeFromWatchedHierarchies(moniker); @@ -350,7 +349,7 @@ private void TryClosingDocumentsForMoniker(string moniker) public Task CheckForAddedFileBeingOpenMaybeAsync(bool useAsync, ImmutableArray newFileNames) { - ForegroundThreadAffinitizedObject.ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); return _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => { @@ -397,7 +396,7 @@ internal void CheckForOpenFilesThatWeMissed() { // It's possible that Roslyn is loading asynchronously after documents were already opened by the user; this is a one-time check for // any of those -- after this point, we are subscribed to events so we'll know of anything else. - _foregroundAffinitization.AssertIsForeground(); + _workspace._threadingContext.ThrowIfNotOnUIThread(); foreach (var (filePath, textBuffer, hierarchy) in _openTextBufferProvider.EnumerateDocumentSet()) { diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index 9f3d95eaca706..5d5366d40dd6b 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -15,6 +15,8 @@ using EnvDTE; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; @@ -74,11 +76,6 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac /// private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1); - /// - /// A to make assertions that stuff is on the right thread. - /// - private readonly ForegroundThreadAffinitizedObject _foregroundObject; - private ImmutableDictionary _projectToHierarchyMap = ImmutableDictionary.Empty; private ImmutableDictionary _projectToGuidMap = ImmutableDictionary.Empty; @@ -120,8 +117,6 @@ public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServicePro _projectionBufferFactoryService = exportProvider.GetExportedValue(); _projectCodeModelFactory = exportProvider.GetExport(); - _foregroundObject = new ForegroundThreadAffinitizedObject(_threadingContext); - _textBufferFactoryService.TextBufferCreated += AddTextBufferCloneServiceToBuffer; _projectionBufferFactoryService.ProjectionBufferCreated += AddTextBufferCloneServiceToBuffer; @@ -135,7 +130,6 @@ public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServicePro new ExternalErrorDiagnosticUpdateSource( this, exportProvider.GetExportedValue(), - exportProvider.GetExportedValue(), exportProvider.GetExportedValue(), exportProvider.GetExportedValue(), _threadingContext), @@ -150,7 +144,7 @@ internal void SubscribeExternalErrorDiagnosticUpdateSourceToSolutionBuildEvents( { // TODO: further understand if this needs the foreground thread for any reason. UIContexts are safe to read from the UI thread; // it's not clear to me why this is being asserted. - _foregroundObject.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_isExternalErrorDiagnosticUpdateSourceSubscribedToSolutionBuildEvents) { @@ -317,7 +311,7 @@ internal override bool TryApplyChanges( Microsoft.CodeAnalysis.Solution newSolution, IProgress progressTracker) { - if (!_foregroundObject.IsForeground()) + if (!_threadingContext.JoinableTaskContext.IsOnMainThread) { throw new InvalidOperationException(ServicesVSResources.VisualStudioWorkspace_TryApplyChanges_cannot_be_called_from_a_background_thread); } @@ -369,7 +363,7 @@ internal bool IsCPSProject(CodeAnalysis.Project project) internal bool IsCPSProject(ProjectId projectId) { - _foregroundObject.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (this.TryGetHierarchy(projectId, out var hierarchy)) { @@ -787,7 +781,7 @@ private void AddDocumentCore(DocumentInfo info, SourceText initialText, TextDocu if (IsWebsite(project)) { - AddDocumentToFolder(project, info.Id, SpecializedCollections.SingletonEnumerable(AppCodeFolderName), info.Name, documentKind, initialText, info.FilePath); + AddDocumentToFolder(project, info.Id, [AppCodeFolderName], info.Name, documentKind, initialText, info.FilePath); } else if (folders.Any()) { @@ -838,9 +832,7 @@ private static IEnumerable FilterFolderForProjectType(EnvDTE.Project pro private static IEnumerable GetAllItems(ProjectItems projectItems) { if (projectItems == null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var items = projectItems.OfType(); return items.Concat(items.SelectMany(i => GetAllItems(i.ProjectItems))); @@ -1087,7 +1079,7 @@ public void OpenDocumentCore(DocumentId documentId, bool activate = true) throw new ArgumentNullException(nameof(documentId)); } - if (!_foregroundObject.IsForeground()) + if (!_threadingContext.JoinableTaskContext.IsOnMainThread) { throw new InvalidOperationException(ServicesVSResources.This_workspace_only_supports_opening_documents_on_the_UI_thread); } @@ -1351,7 +1343,7 @@ internal override Guid GetProjectGuid(ProjectId projectId) internal override void SetDocumentContext(DocumentId documentId) { - _foregroundObject.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); // Note: this method does not actually call into any workspace code here to change the workspace's context. The assumption is updating the running document table or // IVsHierarchies will raise the appropriate events which we are subscribed to. @@ -1475,7 +1467,7 @@ public virtual void EnsureEditableDocuments(IEnumerable documents) internal override bool CanAddProjectReference(ProjectId referencingProject, ProjectId referencedProject) { - _foregroundObject.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (!TryGetHierarchy(referencingProject, out var referencingHierarchy) || !TryGetHierarchy(referencedProject, out var referencedHierarchy)) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl_SourceGenerators.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl_SourceGenerators.cs new file mode 100644 index 0000000000000..d11f95df75d4a --- /dev/null +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl_SourceGenerators.cs @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more 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 System.Linq; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Commanding; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; + +internal abstract partial class VisualStudioWorkspaceImpl +{ + private bool _isSubscribedToSourceGeneratorImpactingEvents; + + public void SubscribeToSourceGeneratorImpactingEvents() + { + _threadingContext.ThrowIfNotOnUIThread(); + if (_isSubscribedToSourceGeneratorImpactingEvents) + return; + + // UIContextImpl requires IVsMonitorSelection service: + if (ServiceProvider.GlobalProvider.GetService(typeof(IVsMonitorSelection)) == null) + return; + + _isSubscribedToSourceGeneratorImpactingEvents = true; + + // This pattern ensures that we are called whenever the build starts/completes even if it is already in progress. + KnownUIContexts.SolutionBuildingContext.WhenActivated(() => + { + KnownUIContexts.SolutionBuildingContext.UIContextChanged += (_, e) => + { + if (!e.Activated) + { + // After a build occurs, transition the solution to a new source generator version. This will + // ensure that any cached SG documents will be re-generated. + this.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false); + } + }; + }); + + KnownUIContexts.SolutionExistsAndFullyLoadedContext.WhenActivated(() => + { + KnownUIContexts.SolutionExistsAndFullyLoadedContext.UIContextChanged += (_, e) => + { + if (e.Activated) + { + // After the solution fully loads, transition the solution to a new source generator version. This + // will ensure that we'll now produce correct SG docs with fully knowledge of all the user's state. + this.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false); + } + }; + }); + + // Whenever the workspace status changes, go attempt to update generators. + var workspaceStatusService = this.Services.GetRequiredService(); + workspaceStatusService.StatusChanged += (_, _) => EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false); + + // Now kick off at least the initial work to run generators. + this.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false); + } + + [Export(typeof(ICommandHandler))] + [ContentType(ContentTypeNames.RoslynContentType)] + [ContentType(ContentTypeNames.XamlContentType)] + [Name(PredefinedCommandHandlerNames.SourceGeneratorSave)] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class SaveCommandHandler() : IChainedCommandHandler + { + public string DisplayName => ServicesVSResources.Roslyn_save_command_handler; + + public CommandState GetCommandState(SaveCommandArgs args, Func nextCommandHandler) + => nextCommandHandler(); + + public void ExecuteCommand(SaveCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext) + { + nextCommandHandler(); + + // After a save happens, enqueue a request to run generators on the projects impacted by the save. + foreach (var projectGroup in args.SubjectBuffer.GetRelatedDocuments().GroupBy(d => d.Project)) + { + if (projectGroup.Key.Solution.Workspace is VisualStudioWorkspaceImpl visualStudioWorkspace) + { + visualStudioWorkspace.EnqueueUpdateSourceGeneratorVersion(projectGroup.Key.Id, forceRegeneration: false); + } + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs b/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs index 6aec52668082b..8185783e7f170 100644 --- a/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs +++ b/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs @@ -120,8 +120,7 @@ private VisualStudioRemoteHostClientProvider( var serviceBroker = brokeredServiceContainer.GetFullAccessServiceBroker(); var configuration = - (_globalOptions.GetOption(RemoteHostOptionsStorage.OOPCoreClr) ? RemoteProcessConfiguration.Core : 0) | - (_globalOptions.GetOption(RemoteHostOptionsStorage.OOPServerGCFeatureFlag) ? RemoteProcessConfiguration.ServerGC : 0); + _globalOptions.GetOption(RemoteHostOptionsStorage.OOPServerGCFeatureFlag) ? RemoteProcessConfiguration.ServerGC : 0; // VS AsyncLazy does not currently support cancellation: var client = await ServiceHubRemoteHostClient.CreateAsync(Services, configuration, _listenerProvider, serviceBroker, _callbackDispatchers, CancellationToken.None).ConfigureAwait(false); diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index 9f14f40d824f8..93730649e413e 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ColorSchemes; using Microsoft.CodeAnalysis.Common; +using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; @@ -179,6 +180,11 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke serviceBrokerContainer.Proffer( WorkspaceProjectFactoryServiceDescriptor.ServiceDescriptor, (_, _, _, _) => ValueTaskFactory.FromResult(new WorkspaceProjectFactoryService(this.ComponentModel.GetService()))); + + // Must be profferred before any C#/VB projects are loaded and the corresponding UI context activated. + serviceBrokerContainer.Proffer( + ManagedHotReloadLanguageServiceDescriptor.Descriptor, + (_, _, _, _) => ValueTaskFactory.FromResult(new ManagedEditAndContinueLanguageServiceBridge(this.ComponentModel.GetService()))); } private async Task LoadOptionPersistersAsync(IComponentModel componentModel, CancellationToken cancellationToken) @@ -267,7 +273,7 @@ private async Task LoadComponentsBackgroundAsync(CancellationToken cancellationT await LoadStackTraceExplorerMenusAsync(cancellationToken).ConfigureAwait(true); // Initialize keybinding reset detector - await ComponentModel.DefaultExportProvider.GetExportedValue().InitializeAsync().ConfigureAwait(true); + await ComponentModel.DefaultExportProvider.GetExportedValue().InitializeAsync(cancellationToken).ConfigureAwait(true); } private async Task LoadStackTraceExplorerMenusAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/SemanticSearch/SemanticSearchFeatureFlag.cs b/src/VisualStudio/Core/Def/SemanticSearch/SemanticSearchFeatureFlag.cs new file mode 100644 index 0000000000000..4b5e4cadb7d7d --- /dev/null +++ b/src/VisualStudio/Core/Def/SemanticSearch/SemanticSearchFeatureFlag.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.Options; + +namespace Microsoft.VisualStudio.LanguageServices; + +internal static class SemanticSearchFeatureFlag +{ + public static readonly Option2 Enabled = new("visual_studio_enable_semantic_search", defaultValue: false); + + /// + /// Context id that indicates that Semantic Search feature is enabled. + /// TODO: remove, workaround for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1985204 + /// + public const string UIContextId = "D5801818-6009-40BE-9204-8897C23D2856"; +} diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index aca53283fce6f..374efed02fbb5 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -859,6 +859,12 @@ Additional information: {1} Search found no results + + Search found no results: no C# or Visual Basic projects are opened + + + Search canceled + Sync Class View @@ -1382,8 +1388,8 @@ Additional information: {1} Extract Base Class - - This file is auto-generated by the generator '{0}' and cannot be edited. + + This file was generated by '{0}' at {1} and cannot be edited. [generated by {0}] @@ -1672,9 +1678,6 @@ Additional information: {1} Run code analysis in separate process (requires restart) - - Run code analysis on latest .NET (requires restart) - Quick Actions @@ -1894,7 +1897,46 @@ Additional information: {1} When types loosely match + + Strike out obsolete symbols + {0} ({1}) + + Automatic. Run generators after any change + + + Source generator execution (requires restart): + + + Source Generators + + + Balanced. Run generators after saving or building + + + Roslyn save command handler + + + Generator running... + + + Rerun generator + + + The project no longer exists + + + Show guides for comments and preprocessor regions + + + Semantic Search Results + + + Semantic Search ({0}) + + + Prefer static anonymous functions + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/Shared/VisualStudioImageIdService.cs b/src/VisualStudio/Core/Def/Shared/VisualStudioImageIdService.cs index ed42e85163342..93e8e460d6340 100644 --- a/src/VisualStudio/Core/Def/Shared/VisualStudioImageIdService.cs +++ b/src/VisualStudio/Core/Def/Shared/VisualStudioImageIdService.cs @@ -38,26 +38,21 @@ public CompositeImage(ImmutableArray layers, IImageHandle [ExportImageIdService(Name = Name)] [Order(Before = DefaultImageIdService.Name)] -internal class VisualStudioImageIdService : ForegroundThreadAffinitizedObject, IImageIdService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VisualStudioImageIdService(IThreadingContext threadingContext, SVsServiceProvider serviceProvider) : IImageIdService { public const string Name = nameof(VisualStudioImageIdService); - private readonly IVsImageService2 _imageService; + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly IVsImageService2 _imageService = (IVsImageService2)serviceProvider.GetService(typeof(SVsImageService)); // We have to keep the image handles around to keep the compound glyph alive. private readonly List _compositeImages = []; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioImageIdService(IThreadingContext threadingContext, SVsServiceProvider serviceProvider) - : base(threadingContext) - { - _imageService = (IVsImageService2)serviceProvider.GetService(typeof(SVsImageService)); - } - public bool TryGetImageId(ImmutableArray tags, out ImageId imageId) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); imageId = GetImageId(tags); return imageId != default; @@ -97,7 +92,7 @@ private static ImageCompositionLayer CreateLayer( private ImageId GetCompositedImageId(params ImageCompositionLayer[] layers) { - this.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); foreach (var compositeImage in _compositeImages) { diff --git a/src/VisualStudio/Core/Def/Snippets/AbstractSnippetCommandHandler.cs b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetCommandHandler.cs index dba6899125244..fdd5a124b81d0 100644 --- a/src/VisualStudio/Core/Def/Snippets/AbstractSnippetCommandHandler.cs +++ b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetCommandHandler.cs @@ -26,8 +26,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Snippets; using Workspace = Microsoft.CodeAnalysis.Workspace; -internal abstract class AbstractSnippetCommandHandler : - ForegroundThreadAffinitizedObject, +internal abstract class AbstractSnippetCommandHandler( + IThreadingContext threadingContext, + EditorOptionsService editorOptionsService, + IVsService textManager) : ICommandHandler, ICommandHandler, ICommandHandler, @@ -35,21 +37,12 @@ internal abstract class AbstractSnippetCommandHandler : ICommandHandler, IChainedCommandHandler { - private readonly EditorOptionsService _editorOptionsService; - private readonly IVsService _textManager; + protected readonly IThreadingContext ThreadingContext = threadingContext; + private readonly EditorOptionsService _editorOptionsService = editorOptionsService; + private readonly IVsService _textManager = textManager; public string DisplayName => FeaturesResources.Snippets; - public AbstractSnippetCommandHandler( - IThreadingContext threadingContext, - EditorOptionsService editorOptionsService, - IVsService textManager) - : base(threadingContext) - { - _editorOptionsService = editorOptionsService; - _textManager = textManager; - } - protected ISnippetExpansionClientFactory GetSnippetExpansionClientFactory(Document document) => document.Project.Services.SolutionServices.GetRequiredService(); @@ -61,7 +54,7 @@ protected virtual bool TryInvokeSnippetPickerOnQuestionMark(ITextView textView, public bool ExecuteCommand(TabKeyCommandArgs args, CommandExecutionContext context) { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (AreSnippetsEnabledWithClient(args, out var snippetExpansionClient) && snippetExpansionClient.TryHandleTab()) { @@ -92,7 +85,7 @@ public bool ExecuteCommand(TabKeyCommandArgs args, CommandExecutionContext conte public CommandState GetCommandState(TabKeyCommandArgs args) { - AssertIsForeground(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (!AreSnippetsEnabled(args)) { @@ -114,7 +107,7 @@ public CommandState GetCommandState(AutomaticLineEnderCommandArgs args, Func _textManager.GetValueOrNullAsync(CancellationToken.None)); + var textManager = this.ThreadingContext.JoinableTaskFactory.Run(() => _textManager.GetValueOrNullAsync(CancellationToken.None)); if (textManager == null) { expansionManager = null; diff --git a/src/VisualStudio/Core/Def/Snippets/AbstractSnippetInfoService.cs b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetInfoService.cs index 39c4945ba8550..4bee6e1386125 100644 --- a/src/VisualStudio/Core/Def/Snippets/AbstractSnippetInfoService.cs +++ b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetInfoService.cs @@ -209,7 +209,7 @@ private ImmutableArray ExtractSnippetInfo(IVsExpansionEnumeration e Marshal.FreeCoTaskMem(pSnippetInfo[0]); } - return snippetListBuilder.ToImmutable(); + return snippetListBuilder.ToImmutableAndClear(); } protected static IImmutableSet GetShortcutsHashFromSnippets(ImmutableArray updatedSnippets) diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetExpansionClient.cs b/src/VisualStudio/Core/Def/Snippets/SnippetExpansionClient.cs index df4d0d18d2353..033fc59795f93 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetExpansionClient.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetExpansionClient.cs @@ -47,7 +47,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Snippets; -internal class SnippetExpansionClient : ForegroundThreadAffinitizedObject, IVsExpansionClient +internal class SnippetExpansionClient : IVsExpansionClient { /// /// The name of a snippet field created for caret placement in Full Method Call snippet sessions when the @@ -59,7 +59,7 @@ internal class SnippetExpansionClient : ForegroundThreadAffinitizedObject, IVsEx /// A generated random string which is used to identify argument completion snippets from other snippets. /// private static readonly string s_fullMethodCallDescriptionSentinel = Guid.NewGuid().ToString("N"); - + private readonly IThreadingContext _threadingContext; private readonly ISnippetExpansionLanguageHelper _languageHelper; private readonly SignatureHelpControllerProvider _signatureHelpControllerProvider; private readonly IEditorCommandHandlerServiceFactory _editorCommandHandlerServiceFactory; @@ -97,8 +97,8 @@ public SnippetExpansionClient( IVsEditorAdaptersFactoryService editorAdaptersFactoryService, ImmutableArray> argumentProviders, EditorOptionsService editorOptionsService) - : base(threadingContext) { + _threadingContext = threadingContext; _languageHelper = languageHelper; TextView = textView; SubjectBuffer = subjectBuffer; @@ -117,7 +117,7 @@ public SnippetExpansionClient( public ImmutableArray GetArgumentProviders(Workspace workspace) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); // TODO: Move this to ArgumentProviderService: https://github.com/dotnet/roslyn/issues/50897 if (_argumentProviders.IsDefault) @@ -141,13 +141,13 @@ public int GetExpansionFunction(IXMLDOMNode xmlFunctionNode, string bstrFieldNam switch (snippetFunctionName) { case "SimpleTypeName": - pFunc = new SnippetFunctionSimpleTypeName(this, SubjectBuffer, bstrFieldName, param, ThreadingContext); + pFunc = new SnippetFunctionSimpleTypeName(this, SubjectBuffer, bstrFieldName, param, _threadingContext); return VSConstants.S_OK; case "ClassName": - pFunc = new SnippetFunctionClassName(this, SubjectBuffer, bstrFieldName, ThreadingContext); + pFunc = new SnippetFunctionClassName(this, SubjectBuffer, bstrFieldName, _threadingContext); return VSConstants.S_OK; case "GenerateSwitchCases": - pFunc = new SnippetFunctionGenerateSwitchCases(this, SubjectBuffer, bstrFieldName, param, ThreadingContext); + pFunc = new SnippetFunctionGenerateSwitchCases(this, SubjectBuffer, bstrFieldName, param, _threadingContext); return VSConstants.S_OK; default: pFunc = null; @@ -157,7 +157,7 @@ public int GetExpansionFunction(IXMLDOMNode xmlFunctionNode, string bstrFieldNam public int FormatSpan(IVsTextLines pBuffer, VsTextSpan[] tsInSurfaceBuffer) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (ExpansionSession == null) { @@ -544,7 +544,7 @@ private bool TryInsertArgumentCompletionSnippet(SnapshotSpan triggerSpan, Snapsh return false; } - var symbols = ThreadingContext.JoinableTaskFactory.Run(() => GetReferencedSymbolsToLeftOfCaretAsync(document, caretPosition: triggerSpan.End, cancellationToken)); + var symbols = _threadingContext.JoinableTaskFactory.Run(() => GetReferencedSymbolsToLeftOfCaretAsync(document, caretPosition: triggerSpan.End, cancellationToken)); var methodSymbols = symbols.OfType().ToImmutableArray(); if (methodSymbols.Any()) @@ -585,7 +585,7 @@ private bool TryInsertArgumentCompletionSnippet(SnapshotSpan triggerSpan, Snapsh static void EnsureRegisteredForModelUpdatedEvents(SnippetExpansionClient client, Controller controller) { // Access to _registeredForSignatureHelpEvents is synchronized on the main thread - client.ThreadingContext.ThrowIfNotOnUIThread(); + client._threadingContext.ThrowIfNotOnUIThread(); if (!client._registeredForSignatureHelpEvents) { @@ -703,7 +703,7 @@ private static XDocument CreateMethodCallSnippet(string methodName, bool include private void OnModelUpdated(object sender, ModelUpdatedEventsArgs e) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (e.NewModel is null) { @@ -738,7 +738,7 @@ private void OnModelUpdated(object sender, ModelUpdatedEventsArgs e) // TODO: The following blocks the UI thread without cancellation, but it only occurs when an argument value // completion session is active, which is behind an experimental feature flag. // https://github.com/dotnet/roslyn/issues/50634 - var compilation = ThreadingContext.JoinableTaskFactory.Run(() => document.Project.GetRequiredCompilationAsync(CancellationToken.None)); + var compilation = _threadingContext.JoinableTaskFactory.Run(() => document.Project.GetRequiredCompilationAsync(CancellationToken.None)); var newSymbolKey = (e.NewModel.SelectedItem as AbstractSignatureHelpProvider.SymbolKeySignatureHelpItem)?.SymbolKey ?? default; var newSymbol = newSymbolKey.Resolve(compilation, cancellationToken: CancellationToken.None).GetAnySymbol(); if (newSymbol is not IMethodSymbol method) @@ -772,7 +772,7 @@ private static async Task> GetReferencedSymbolsToLeftOfC /// A cancellation token the operation may observe. public void MoveToSpecificMethod(IMethodSymbol method, CancellationToken cancellationToken) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (ExpansionSession is null) { @@ -902,7 +902,7 @@ public void MoveToSpecificMethod(IMethodSymbol method, CancellationToken cancell foreach (var provider in GetArgumentProviders(document.Project.Solution.Workspace)) { var context = new ArgumentContext(provider, semanticModel, position, parameter, value, cancellationToken); - ThreadingContext.JoinableTaskFactory.Run(() => provider.ProvideArgumentAsync(context)); + _threadingContext.JoinableTaskFactory.Run(() => provider.ProvideArgumentAsync(context)); if (context.DefaultValue is not null) { diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs index 3727ab638e2b5..d7145bc6db4ea 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.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.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -28,18 +27,13 @@ public AbstractSnippetFunction(SnippetExpansionClient snippetExpansionClient, IT _threadingContext = threadingContext; } - protected bool TryGetDocument(out Document document) - { - document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - return document != null; - } + protected Document? GetDocument(CancellationToken cancellationToken) + => _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges()?.WithFrozenPartialSemantics(cancellationToken); private int GetDefaultValue(CancellationToken cancellationToken, out string value, out int hasDefaultValue) { - var (ExitCode, Value, HasDefaultValue) = _threadingContext.JoinableTaskFactory.Run(() => GetDefaultValueAsync(cancellationToken)); - value = Value; - hasDefaultValue = HasDefaultValue; - return ExitCode; + (var exitCode, value, hasDefaultValue) = _threadingContext.JoinableTaskFactory.Run(() => GetDefaultValueAsync(cancellationToken)); + return exitCode; } protected virtual Task<(int ExitCode, string Value, int HasDefaultValue)> GetDefaultValueAsync(CancellationToken cancellationToken) @@ -49,10 +43,8 @@ private int GetDefaultValue(CancellationToken cancellationToken, out string valu private int GetCurrentValue(CancellationToken cancellationToken, out string value, out int hasCurrentValue) { - var (ExitCode, Value, HasCurrentValue) = _threadingContext.JoinableTaskFactory.Run(() => GetCurrentValueAsync(cancellationToken)); - value = Value; - hasCurrentValue = HasCurrentValue; - return ExitCode; + (var exitCode, value, hasCurrentValue) = _threadingContext.JoinableTaskFactory.Run(() => GetCurrentValueAsync(cancellationToken)); + return exitCode; } protected virtual Task<(int ExitCode, string Value, int HasCurrentValue)> GetCurrentValueAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs index a732df3a371ef..015dd6d99bc9e 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs @@ -28,29 +28,22 @@ public SnippetFunctionClassName(SnippetExpansionClient snippetExpansionClient, I { var hasDefaultValue = 0; var value = string.Empty; - if (!TryGetDocument(out var document)) - { + var document = GetDocument(cancellationToken); + if (document is null) return (VSConstants.E_FAIL, value, hasDefaultValue); - } var surfaceBufferFieldSpan = new VsTextSpan[1]; if (snippetExpansionClient.ExpansionSession.GetFieldSpan(FieldName, surfaceBufferFieldSpan) != VSConstants.S_OK) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } if (!snippetExpansionClient.TryGetSubjectBufferSpan(surfaceBufferFieldSpan[0], out var subjectBufferFieldSpan)) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } var snippetFunctionService = document.Project.GetRequiredLanguageService(); value = await snippetFunctionService.GetContainingClassNameAsync(document, subjectBufferFieldSpan.Start.Position, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(value)) - { hasDefaultValue = 1; - } return (VSConstants.S_OK, value, hasDefaultValue); } diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs index 6044dce6fe387..526e0a7091825 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs @@ -41,10 +41,9 @@ protected override int FieldChanged(string field, out int requeryFunction) protected override async Task<(int ExitCode, string Value, int HasCurrentValue)> GetCurrentValueAsync(CancellationToken cancellationToken) { - if (!TryGetDocument(out var document)) - { + var document = GetDocument(cancellationToken); + if (document is null) return (VSConstants.S_OK, string.Empty, HasCurrentValue: 0); - } // If the switch expression is invalid, still show the default case var hasCurrentValue = 1; @@ -60,9 +59,7 @@ protected override int FieldChanged(string field, out int requeryFunction) var value = await snippetFunctionService.GetSwitchExpansionAsync(document, caseGenerationSpan.Value, switchExpressionSpan.Value, simplifierOptions, cancellationToken).ConfigureAwait(false); if (value == null) - { return (VSConstants.S_OK, snippetFunctionService.SwitchDefaultCaseForm, hasCurrentValue); - } return (VSConstants.S_OK, value, hasCurrentValue); } diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs index 81ce994883a1e..77665d5efd398 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs @@ -35,23 +35,18 @@ public SnippetFunctionSimpleTypeName( { var value = _fullyQualifiedName; var hasDefaultValue = 1; - if (!TryGetDocument(out var document)) - { + var document = GetDocument(cancellationToken); + if (document is null) return (VSConstants.E_FAIL, value, hasDefaultValue); - } if (!TryGetFieldSpan(out var fieldSpan)) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } var simplifierOptions = await document.GetSimplifierOptionsAsync(snippetExpansionClient.EditorOptionsService.GlobalOptions, cancellationToken).ConfigureAwait(false); var simplifiedTypeName = await SnippetFunctionService.GetSimplifiedTypeNameAsync(document, fieldSpan.Value, _fullyQualifiedName, simplifierOptions, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(simplifiedTypeName)) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } return (VSConstants.S_OK, simplifiedTypeName!, hasDefaultValue); } diff --git a/src/VisualStudio/Core/Def/SymbolSearch/AbstractDelayStartedService.cs b/src/VisualStudio/Core/Def/SymbolSearch/AbstractDelayStartedService.cs index e76f19ec2fe0e..7c3142c6eca8d 100644 --- a/src/VisualStudio/Core/Def/SymbolSearch/AbstractDelayStartedService.cs +++ b/src/VisualStudio/Core/Def/SymbolSearch/AbstractDelayStartedService.cs @@ -22,8 +22,9 @@ namespace Microsoft.VisualStudio.LanguageServices.SymbolSearch; /// to run the core codepath if the user has not enabled the features /// that need it. That helps us avoid loading dlls unnecessarily and bloating the VS memory space. /// -internal abstract class AbstractDelayStartedService : ForegroundThreadAffinitizedObject +internal abstract class AbstractDelayStartedService { + protected readonly IThreadingContext ThreadingContext; private readonly IGlobalOptionService _globalOptions; protected readonly VisualStudioWorkspaceImpl Workspace; @@ -56,8 +57,8 @@ protected AbstractDelayStartedService( IAsynchronousOperationListenerProvider listenerProvider, Option2 featureEnabledOption, ImmutableArray> perLanguageOptions) - : base(threadingContext) { + ThreadingContext = threadingContext; _globalOptions = globalOptions; Workspace = workspace; _featureEnabledOption = featureEnabledOption; diff --git a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioDiagnosticListSuppressionStateService.cs b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioDiagnosticListSuppressionStateService.cs index 51e583cf2dc4d..ffba254c0b0e0 100644 --- a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioDiagnosticListSuppressionStateService.cs +++ b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioDiagnosticListSuppressionStateService.cs @@ -326,7 +326,7 @@ public async Task> GetSelectedItemsAsync(bool isA } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private static async Task> GetFilePathToDocumentMapAsync(Project project, CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs index f627bc807b105..efcbbc6b94d69 100644 --- a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs +++ b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs @@ -214,14 +214,9 @@ await _editHandlerService.ApplyAsync( scope.GetCodeAnalysisProgress(), context.UserCancellationToken).ConfigureAwait(false); + // Kick off diagnostic re-analysis for affected document so that the configured diagnostic gets refreshed. if (selectedDiagnostic.DocumentId != null) - { - // Kick off diagnostic re-analysis for affected document so that the configured diagnostic gets refreshed. - _ = Task.Run(() => - { - _diagnosticService.Reanalyze(_workspace, projectIds: null, documentIds: SpecializedCollections.SingletonEnumerable(selectedDiagnostic.DocumentId), highPriority: true); - }); - } + _diagnosticService.RequestDiagnosticRefresh(); } catch (OperationCanceledException) { diff --git a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs index 62701ab03fb37..a1335ea259327 100644 --- a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs +++ b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs @@ -199,7 +199,7 @@ private async Task> GetAllBuildDiagnosticsAsync(F } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private static string GetFixTitle(bool isAddSuppression) @@ -413,11 +413,7 @@ await _editHandlerService.ApplyAsync( cancellationToken).ConfigureAwait(false); // Kick off diagnostic re-analysis for affected projects so that diagnostics gets refreshed. - _ = Task.Run(() => - { - var reanalyzeDocuments = diagnosticsToFix.Select(d => d.DocumentId).WhereNotNull().Distinct(); - _diagnosticService.Reanalyze(_workspace, projectIds: null, documentIds: reanalyzeDocuments, highPriority: true); - }); + _diagnosticService.RequestDiagnosticRefresh(); } } catch (OperationCanceledException) diff --git a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs index 4510c3f95c160..d5440f5d2d032 100644 --- a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -10,8 +10,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.PooledObjects; @@ -20,12 +22,8 @@ using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.Utilities; -#pragma warning disable CA1200 // Avoid using cref tags with a prefix - namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; -using ProjectErrorMap = ImmutableDictionary>; - /// /// Diagnostic source for warnings and errors reported from explicit build command invocations in Visual Studio. /// VS workspaces calls into us when a build is invoked or completed in Visual Studio. @@ -36,35 +34,24 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; /// It raises events about diagnostic updates, which eventually trigger the "Build + Intellisense" and "Build only" error list diagnostic /// sources to update the reported diagnostics. /// -internal sealed class ExternalErrorDiagnosticUpdateSource : IDiagnosticUpdateSource, IDisposable +internal sealed class ExternalErrorDiagnosticUpdateSource : IDisposable { private readonly Workspace _workspace; private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly IBuildOnlyDiagnosticsService _buildOnlyDiagnosticsService; private readonly IGlobalOperationNotificationService _notificationService; - private readonly CancellationToken _disposalToken; /// /// Task queue to serialize all the work for errors reported by build. /// represents the state from build errors, /// which is built up and processed in serialized fashion on this task queue. /// - private readonly TaskQueue _taskQueue; - - /// - /// Task queue to serialize all the post-build and post error list refresh tasks. - /// Error list refresh requires build/live diagnostics de-duping to complete, which happens during - /// . - /// Computationally expensive tasks such as writing build errors into persistent storage, - /// invoking background analysis on open files/solution after build completes, etc. - /// are added to this task queue to help ensure faster error list refresh. - /// - private readonly TaskQueue _postBuildAndErrorListRefreshTaskQueue; + private readonly AsyncBatchingWorkQueue> _taskQueue; // Gate for concurrent access and fields guarded with this gate. private readonly object _gate = new(); private InProgressState? _stateDoNotAccessDirectly; - private readonly CancellationSeries _activeCancellationSeriesDoNotAccessDirectly = new(); + private readonly CancellationSeries _activeCancellationSeriesDoNotAccessDirectly; /// /// Latest diagnostics reported during current or last build. @@ -76,13 +63,11 @@ internal sealed class ExternalErrorDiagnosticUpdateSource : IDiagnosticUpdateSou public ExternalErrorDiagnosticUpdateSource( VisualStudioWorkspace workspace, IDiagnosticAnalyzerService diagnosticService, - IDiagnosticUpdateSourceRegistrationService registrationService, IGlobalOperationNotificationService notificationService, IAsynchronousOperationListenerProvider listenerProvider, IThreadingContext threadingContext) : this(workspace, diagnosticService, notificationService, listenerProvider.GetListener(FeatureAttribute.ErrorList), threadingContext.DisposalToken) { - registrationService.Register(this); } /// @@ -96,9 +81,12 @@ internal ExternalErrorDiagnosticUpdateSource( CancellationToken disposalToken) { // use queue to serialize work. no lock needed - _taskQueue = new TaskQueue(listener, TaskScheduler.Default); - _postBuildAndErrorListRefreshTaskQueue = new TaskQueue(listener, TaskScheduler.Default); - _disposalToken = disposalToken; + _taskQueue = new AsyncBatchingWorkQueue>( + TimeSpan.Zero, + ProcessTaskQueueItemsAsync, + listener, + disposalToken); + _activeCancellationSeriesDoNotAccessDirectly = new(disposalToken); _workspace = workspace; _workspace.WorkspaceChanged += OnWorkspaceChanged; @@ -111,24 +99,6 @@ internal ExternalErrorDiagnosticUpdateSource( public DiagnosticAnalyzerInfoCache AnalyzerInfoCache => _diagnosticService.AnalyzerInfoCache; - /// - /// Event generated from the serialized whenever the build progress in Visual Studio changes. - /// Events are guaranteed to be generated in a serial fashion, but may be invoked on any thread. - /// - public event EventHandler? BuildProgressChanged; - - /// - /// Event generated from the serialized whenever build-only diagnostics are reported during a build in Visual Studio. - /// These diagnostics are not supported from intellisense and only get refreshed during actual build. - /// - public event EventHandler>? DiagnosticsUpdated; - - /// - /// Event generated from the serialized whenever build-only diagnostics are cleared during a build in Visual Studio. - /// These diagnostics are not supported from intellisense and only get refreshed during actual build. - /// - public event EventHandler DiagnosticsCleared { add { } remove { } } - /// /// Indicates if a build is currently in progress inside Visual Studio. /// @@ -158,14 +128,12 @@ public ImmutableArray GetBuildErrors() public bool IsSupportedDiagnosticId(ProjectId projectId, string id) => GetBuildInProgressState()?.IsSupportedDiagnosticId(projectId, id) ?? false; - private void OnBuildProgressChanged(InProgressState? state, BuildProgress buildProgress) + private void OnBuildProgressChanged(InProgressState? state) { if (state != null) { _lastBuiltResult = state.GetBuildErrors(); } - - RaiseBuildProgressChanged(buildProgress); } public void ClearErrors(ProjectId projectId) @@ -175,16 +143,19 @@ public void ClearErrors(ProjectId projectId) // Update the state to clear diagnostics and raise corresponding diagnostic updated events // on a serialized task queue. - _taskQueue.ScheduleTask(nameof(ClearErrors), async () => + _taskQueue.AddWork(async cancellationToken => { if (state == null) { // TODO: Is it possible that ClearErrors can be invoked while the build is not in progress? // We fallback to current solution in the workspace and clear errors for the project. - await ClearErrorsCoreAsync(projectId, _workspace.CurrentSolution, state).ConfigureAwait(false); + await ClearErrorsCoreAsync(projectId, _workspace.CurrentSolution, state, cancellationToken).ConfigureAwait(false); } else { + if (state.CancellationToken.IsCancellationRequested) + return; + // We are going to clear the diagnostics for the current project. // Additionally, we clear errors for all projects that transitively depend on this project. // Otherwise, fixing errors in core projects in dependency chain will leave back stale diagnostics in dependent projects. @@ -192,13 +163,11 @@ public void ClearErrors(ProjectId projectId) // First check if we already cleared the diagnostics for this project when processing a referenced project. // If so, we don't need to clear diagnostics for it again. if (state.WereProjectErrorsCleared(projectId)) - { return; - } var solution = state.Solution; - await ClearErrorsCoreAsync(projectId, solution, state).ConfigureAwait(false); + await ClearErrorsCoreAsync(projectId, solution, state, cancellationToken).ConfigureAwait(false); var transitiveProjectIds = solution.GetProjectDependencyGraph().GetProjectsThatTransitivelyDependOnThisProject(projectId); foreach (var projectId in transitiveProjectIds) @@ -208,14 +177,14 @@ public void ClearErrors(ProjectId projectId) continue; } - await ClearErrorsCoreAsync(projectId, solution, state).ConfigureAwait(false); + await ClearErrorsCoreAsync(projectId, solution, state, cancellationToken).ConfigureAwait(false); } } - }, GetApplicableCancellationToken(state)); + }); return; - async ValueTask ClearErrorsCoreAsync(ProjectId projectId, Solution solution, InProgressState? state) + async Task ClearErrorsCoreAsync(ProjectId projectId, Solution solution, InProgressState? state, CancellationToken cancellationToken) { Debug.Assert(state == null || !state.WereProjectErrorsCleared(projectId)); @@ -225,17 +194,13 @@ async ValueTask ClearErrorsCoreAsync(ProjectId projectId, Solution solution, InP // when 'ClearErrors' is invoked for multiple dependent projects. // Finally, we update build progress state so error list gets refreshed. - using (var argsBuilder = TemporaryArray.Empty) - { - AddArgsToClearBuildOnlyProjectErrors(ref argsBuilder.AsRef(), solution, projectId); - ProcessAndRaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - } - - await SetLiveErrorsForProjectAsync(projectId, ImmutableArray.Empty, GetApplicableCancellationToken(state)).ConfigureAwait(false); + using var argsBuilder = TemporaryArray.Empty; + AddArgsToClearBuildOnlyProjectErrors(ref argsBuilder.AsRef(), solution, projectId); + await ProcessAndRaiseDiagnosticsUpdatedAsync(argsBuilder.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); state?.MarkErrorsCleared(projectId); - OnBuildProgressChanged(state, BuildProgress.Updated); + OnBuildProgressChanged(state); } } @@ -246,9 +211,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) switch (e.Kind) { case WorkspaceChangeKind.SolutionAdded: - _taskQueue.ScheduleTask( - "OnSolutionAdded", - () => + _taskQueue.AddWork(async cancellationToken => { using var argsBuilder = TemporaryArray.Empty; foreach (var projectId in e.OldSolution.ProjectIds) @@ -256,17 +219,14 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) AddArgsToClearBuildOnlyProjectErrors(ref argsBuilder.AsRef(), e.OldSolution, projectId); } - ProcessAndRaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - }, - _disposalToken); + await ProcessAndRaiseDiagnosticsUpdatedAsync(argsBuilder.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + }); break; case WorkspaceChangeKind.SolutionRemoved: case WorkspaceChangeKind.SolutionCleared: case WorkspaceChangeKind.SolutionReloaded: - _taskQueue.ScheduleTask( - "OnSolutionChanged", - () => + _taskQueue.AddWork(async cancellationToken => { using var argsBuilder = TemporaryArray.Empty; foreach (var projectId in e.OldSolution.ProjectIds) @@ -274,22 +234,18 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) AddArgsToClearBuildOnlyProjectErrors(ref argsBuilder.AsRef(), e.OldSolution, projectId); } - ProcessAndRaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - }, - _disposalToken); + await ProcessAndRaiseDiagnosticsUpdatedAsync(argsBuilder.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + }); break; case WorkspaceChangeKind.ProjectRemoved: case WorkspaceChangeKind.ProjectReloaded: - _taskQueue.ScheduleTask( - "OnProjectChanged", - () => + _taskQueue.AddWork(async cancellationToken => { using var argsBuilder = TemporaryArray.Empty; AddArgsToClearBuildOnlyProjectErrors(ref argsBuilder.AsRef(), e.OldSolution, e.ProjectId); - ProcessAndRaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - }, - _disposalToken); + await ProcessAndRaiseDiagnosticsUpdatedAsync(argsBuilder.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + }); break; case WorkspaceChangeKind.DocumentRemoved: @@ -298,15 +254,12 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) case WorkspaceChangeKind.AdditionalDocumentReloaded: case WorkspaceChangeKind.AnalyzerConfigDocumentRemoved: case WorkspaceChangeKind.AnalyzerConfigDocumentReloaded: - _taskQueue.ScheduleTask( - "OnDocumentRemoved", - () => + _taskQueue.AddWork(async cancellationToken => { using var argsBuilder = TemporaryArray.Empty; AddArgsToClearBuildOnlyDocumentErrors(ref argsBuilder.AsRef(), e.OldSolution, e.ProjectId, e.DocumentId); - ProcessAndRaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - }, - _disposalToken); + await ProcessAndRaiseDiagnosticsUpdatedAsync(argsBuilder.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + }); break; case WorkspaceChangeKind.DocumentChanged: @@ -318,15 +271,12 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) // do not get automatically removed/refreshed while typing. // See https://github.com/dotnet/docs/issues/26708 and https://github.com/dotnet/roslyn/issues/64659 // for additional details. - _taskQueue.ScheduleTask( - "OnDocumentChanged", - () => + _taskQueue.AddWork(async cancellationToken => { using var argsBuilder = TemporaryArray.Empty; AddArgsToClearBuildOnlyDocumentErrors(ref argsBuilder.AsRef(), e.OldSolution, e.ProjectId, e.DocumentId); - ProcessAndRaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - }, - _disposalToken); + await ProcessAndRaiseDiagnosticsUpdatedAsync(argsBuilder.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + }); break; case WorkspaceChangeKind.ProjectAdded: @@ -355,33 +305,27 @@ internal void OnSolutionBuildCompleted() var inProgressState = ClearInProgressState(); // Enqueue build/live sync in the queue. - _taskQueue.ScheduleTask("OnSolutionBuild", async () => + _taskQueue.AddWork(async cancellationToken => { - try - { - // nothing to do - if (inProgressState == null) - { - return; - } + // nothing to do + if (inProgressState == null) + return; - // Mark the status as updated to refresh error list before we invoke 'SyncBuildErrorsAndReportAsync', which can take some time to complete. - OnBuildProgressChanged(inProgressState, BuildProgress.Updated); + if (inProgressState.CancellationToken.IsCancellationRequested) + return; - // We are about to update live analyzer data using one from build. - // pause live analyzer - using var operation = _notificationService.Start("BuildDone"); - if (_diagnosticService is DiagnosticAnalyzerService diagnosticService) - await SyncBuildErrorsAndReportOnBuildCompletedAsync(diagnosticService, inProgressState).ConfigureAwait(false); + // Mark the status as updated to refresh error list before we invoke 'SyncBuildErrorsAndReportAsync', which can take some time to complete. + OnBuildProgressChanged(inProgressState); - // Mark build as complete. - OnBuildProgressChanged(inProgressState, BuildProgress.Done); - } - finally - { - await _postBuildAndErrorListRefreshTaskQueue.LastScheduledTask.ConfigureAwait(false); - } - }, GetApplicableCancellationToken(inProgressState)); + // We are about to update live analyzer data using one from build. + // pause live analyzer + using var operation = _notificationService.Start("BuildDone"); + if (_diagnosticService is DiagnosticAnalyzerService) + await SyncBuildErrorsAndReportOnBuildCompletedAsync(inProgressState, cancellationToken).ConfigureAwait(false); + + // Mark build as complete. + OnBuildProgressChanged(inProgressState); + }); } /// @@ -389,55 +333,68 @@ internal void OnSolutionBuildCompleted() /// It raises diagnostic update events for both the Build-only diagnostics and Build + Intellisense diagnostics /// in the error list. /// - private ValueTask SyncBuildErrorsAndReportOnBuildCompletedAsync(DiagnosticAnalyzerService diagnosticService, InProgressState inProgressState) + private async Task SyncBuildErrorsAndReportOnBuildCompletedAsync(InProgressState inProgressState, CancellationToken cancellationToken) { - var solution = inProgressState.Solution; - var cancellationToken = inProgressState.CancellationToken; - var (allLiveErrors, pendingLiveErrorsToSync) = inProgressState.GetLiveErrors(); - - // Raise events for build only errors - using var argsBuilder = TemporaryArray.Empty; - var buildErrors = GetBuildErrors().Except(allLiveErrors).GroupBy(k => k.DocumentId); - foreach (var group in buildErrors) + // Allow the queue to be canceled, or this particular item to be canceled. Because we're creating a specialized + // token here, we need to wrap with our own try/catch to make sure that token doesn't bubble out. + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(inProgressState.CancellationToken, cancellationToken); + + try + { + await SyncBuildErrorsAndReportOnBuildCompletedWorkerAsync(inProgressState, linkedTokenSource.Token).ConfigureAwait(false); + } + catch (OperationCanceledException ex) when (ExceptionUtilities.IsCurrentOperationBeingCancelled(ex, linkedTokenSource.Token)) + { + } + + return; + + async Task SyncBuildErrorsAndReportOnBuildCompletedWorkerAsync(InProgressState inProgressState, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + var solution = inProgressState.Solution; + var allLiveErrors = inProgressState.GetLiveErrors(); - if (group.Key == null) + // Raise events for build only errors + using var argsBuilder = TemporaryArray.Empty; + var buildErrors = GetBuildErrors().Except(allLiveErrors).GroupBy(k => k.DocumentId); + foreach (var group in buildErrors) { - foreach (var projectGroup in group.GroupBy(g => g.ProjectId)) + cancellationToken.ThrowIfCancellationRequested(); + + if (group.Key == null) { - Contract.ThrowIfNull(projectGroup.Key); - argsBuilder.Add(CreateArgsToReportBuildErrors(projectGroup.Key, solution, projectGroup.ToImmutableArray())); + foreach (var projectGroup in group.GroupBy(g => g.ProjectId)) + { + Contract.ThrowIfNull(projectGroup.Key); + argsBuilder.Add(CreateArgsToReportBuildErrors(projectGroup.Key, solution, projectGroup.ToImmutableArray())); + } + + continue; } - continue; + argsBuilder.Add(CreateArgsToReportBuildErrors(group.Key, solution, group.ToImmutableArray())); } - argsBuilder.Add(CreateArgsToReportBuildErrors(group.Key, solution, group.ToImmutableArray())); + await ProcessAndRaiseDiagnosticsUpdatedAsync(argsBuilder.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); } - - ProcessAndRaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - - // Report pending live errors - return diagnosticService.SynchronizeWithBuildAsync(_workspace, pendingLiveErrorsToSync, _postBuildAndErrorListRefreshTaskQueue, onBuildCompleted: true, cancellationToken); } - private DiagnosticsUpdatedArgs CreateArgsToReportBuildErrors(T item, Solution solution, ImmutableArray buildErrors) + private static DiagnosticsUpdatedArgs CreateArgsToReportBuildErrors(T item, Solution solution, ImmutableArray buildErrors) { if (item is ProjectId projectId) { - return CreateDiagnosticsCreatedArgs(projectId, solution, projectId, documentId: null, buildErrors); + return CreateDiagnosticsCreatedArgs(solution, projectId, documentId: null, buildErrors); } RoslynDebug.Assert(item is DocumentId); var documentId = (DocumentId)(object)item; - return CreateDiagnosticsCreatedArgs(documentId, solution, documentId.ProjectId, documentId, buildErrors); + return CreateDiagnosticsCreatedArgs(solution, documentId.ProjectId, documentId, buildErrors); } - private void AddArgsToClearBuildOnlyProjectErrors(ref TemporaryArray builder, Solution solution, ProjectId? projectId) + private static void AddArgsToClearBuildOnlyProjectErrors(ref TemporaryArray builder, Solution solution, ProjectId? projectId) { // Remove all project errors - builder.Add(CreateDiagnosticsRemovedArgs(projectId, solution, projectId, documentId: null)); + builder.Add(CreateDiagnosticsRemovedArgs(solution, projectId, documentId: null)); var project = solution.GetProject(projectId); if (project == null) @@ -452,8 +409,8 @@ private void AddArgsToClearBuildOnlyProjectErrors(ref TemporaryArray builder, Solution solution, ProjectId? projectId, DocumentId? documentId) - => builder.Add(CreateDiagnosticsRemovedArgs(documentId, solution, projectId, documentId)); + private static void AddArgsToClearBuildOnlyDocumentErrors(ref TemporaryArray builder, Solution solution, ProjectId? projectId, DocumentId? documentId) + => builder.Add(CreateDiagnosticsRemovedArgs(solution, projectId, documentId)); public void AddNewErrors(ProjectId projectId, DiagnosticData diagnostic) { @@ -462,11 +419,13 @@ public void AddNewErrors(ProjectId projectId, DiagnosticData diagnostic) // Capture state that will be processed in background thread. var state = GetOrCreateInProgressState(); - _taskQueue.ScheduleTask("Project New Errors", async () => + _taskQueue.AddWork(cancellationToken => { - await ReportPreviousProjectErrorsIfRequiredAsync(projectId, state).ConfigureAwait(false); - state.AddError(projectId, diagnostic); - }, state.CancellationToken); + if (!state.CancellationToken.IsCancellationRequested) + state.AddError(projectId, diagnostic); + + return Task.CompletedTask; + }); } public void AddNewErrors(DocumentId documentId, DiagnosticData diagnostic) @@ -476,11 +435,13 @@ public void AddNewErrors(DocumentId documentId, DiagnosticData diagnostic) // Capture state that will be processed in background thread. var state = GetOrCreateInProgressState(); - _taskQueue.ScheduleTask("Document New Errors", async () => + _taskQueue.AddWork(cancellationToken => { - await ReportPreviousProjectErrorsIfRequiredAsync(documentId.ProjectId, state).ConfigureAwait(false); - state.AddError(documentId, diagnostic); - }, state.CancellationToken); + if (!state.CancellationToken.IsCancellationRequested) + state.AddError(documentId, diagnostic); + + return Task.CompletedTask; + }); } public void AddNewErrors( @@ -492,58 +453,19 @@ public void AddNewErrors( // Capture state that will be processed in background thread var state = GetOrCreateInProgressState(); - _taskQueue.ScheduleTask("Project New Errors", async () => + _taskQueue.AddWork(cancellationToken => { - await ReportPreviousProjectErrorsIfRequiredAsync(projectId, state).ConfigureAwait(false); + if (state.CancellationToken.IsCancellationRequested) + return Task.CompletedTask; foreach (var kv in documentErrorMap) state.AddErrors(kv.Key, kv.Value); state.AddErrors(projectId, projectErrors); - }, state.CancellationToken); - } - - /// - /// This method is invoked from all overloads before it adds the new errors to the in progress state. - /// It checks if build reported errors for a different project then the previous callback to report errors. - /// This provides a good checkpoint to de-dupe build and live errors for lastProjectId and - /// raise diagnostic updated events for that project. - /// 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 ValueTask ReportPreviousProjectErrorsIfRequiredAsync(ProjectId projectId, InProgressState state) - { - if (state.TryGetLastProjectWithReportedErrors() is ProjectId lastProjectId && - lastProjectId != projectId) - { - return SetLiveErrorsForProjectAsync(lastProjectId, state); - } - - return default; + return Task.CompletedTask; + }); } - 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 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); - return diagnosticAnalyzerService.SynchronizeWithBuildAsync(_workspace, map, _postBuildAndErrorListRefreshTaskQueue, onBuildCompleted: false, cancellationToken); - } - - return default; - } - - private CancellationToken GetApplicableCancellationToken(InProgressState? state) - => state?.CancellationToken ?? _disposalToken; - private InProgressState? GetBuildInProgressState() { lock (_gate) @@ -572,65 +494,58 @@ private InProgressState GetOrCreateInProgressState() // We take current snapshot of solution when the state is first created. and through out this code, we use this snapshot. // Since we have no idea what actual snapshot of solution the out of proc build has picked up, it doesn't remove the race we can have // between build and diagnostic service, but this at least make us to consistent inside of our code. - _stateDoNotAccessDirectly = new InProgressState(this, _workspace.CurrentSolution, _activeCancellationSeriesDoNotAccessDirectly.CreateNext(_disposalToken)); - OnBuildProgressChanged(_stateDoNotAccessDirectly, BuildProgress.Started); + _stateDoNotAccessDirectly = new InProgressState(this, _workspace.CurrentSolution, _activeCancellationSeriesDoNotAccessDirectly.CreateNext()); + OnBuildProgressChanged(_stateDoNotAccessDirectly); } return _stateDoNotAccessDirectly; } } - private DiagnosticsUpdatedArgs CreateDiagnosticsCreatedArgs(object? id, Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableArray items) + private static DiagnosticsUpdatedArgs CreateDiagnosticsCreatedArgs(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableArray items) { - return DiagnosticsUpdatedArgs.DiagnosticsCreated(CreateArgumentKey(id), _workspace, solution, projectId, documentId, items); + return DiagnosticsUpdatedArgs.DiagnosticsCreated(solution, projectId, documentId, items); } - private DiagnosticsUpdatedArgs CreateDiagnosticsRemovedArgs(object? id, Solution solution, ProjectId? projectId, DocumentId? documentId) + private static DiagnosticsUpdatedArgs CreateDiagnosticsRemovedArgs(Solution solution, ProjectId? projectId, DocumentId? documentId) { - return DiagnosticsUpdatedArgs.DiagnosticsRemoved(CreateArgumentKey(id), _workspace, solution, projectId, documentId); + return DiagnosticsUpdatedArgs.DiagnosticsRemoved(solution, projectId, documentId); } - private void ProcessAndRaiseDiagnosticsUpdated(ImmutableArray argsCollection) + private async Task ProcessAndRaiseDiagnosticsUpdatedAsync( + ImmutableArray argsCollection, + CancellationToken cancellationToken) { if (argsCollection.IsEmpty) - { return; - } foreach (var args in argsCollection) { if (args.Kind == DiagnosticsUpdatedKind.DiagnosticsCreated) { - RoslynDebug.AssertNotNull(args.Solution); - _buildOnlyDiagnosticsService.AddBuildOnlyDiagnostics(args.Solution, args.ProjectId, args.DocumentId, args.Diagnostics); + Contract.ThrowIfNull(args.Solution); + if (args.DocumentId != null) + await _buildOnlyDiagnosticsService.AddBuildOnlyDiagnosticsAsync(args.DocumentId, args.Diagnostics, cancellationToken).ConfigureAwait(false); } else if (args.Kind == DiagnosticsUpdatedKind.DiagnosticsRemoved) { - RoslynDebug.AssertNotNull(args.Solution); - _buildOnlyDiagnosticsService.ClearBuildOnlyDiagnostics(args.Solution, args.ProjectId, args.DocumentId); + Contract.ThrowIfNull(args.Solution); + var project = args.Solution.GetProject(args.ProjectId); + if (project != null) + await _buildOnlyDiagnosticsService.ClearBuildOnlyDiagnosticsAsync(project, args.DocumentId, cancellationToken).ConfigureAwait(false); } } - - DiagnosticsUpdated?.Invoke(this, argsCollection); } - private static ArgumentKey CreateArgumentKey(object? id) => new(id); - - private void RaiseBuildProgressChanged(BuildProgress progress) - => BuildProgressChanged?.Invoke(this, progress); - - public bool SupportGetDiagnostics { get { return false; } } + private async ValueTask ProcessTaskQueueItemsAsync(ImmutableSegmentedList> list, CancellationToken cancellationToken) + { + foreach (var workItem in list) + await workItem(cancellationToken).ConfigureAwait(false); + } internal TestAccessor GetTestAccessor() => new(this); - internal enum BuildProgress - { - Started, - Updated, - Done - } - internal readonly struct TestAccessor(ExternalErrorDiagnosticUpdateSource instance) { internal void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) @@ -684,22 +599,12 @@ private sealed class InProgressState /// private readonly HashSet _projectsWithErrorsCleared = []; - /// - /// Set of projects for which we have reported all intellisense/live diagnostics. - /// - private readonly HashSet _projectsWithAllLiveErrorsReported = []; - /// /// Set of projects which have at least one project or document diagnostic in /// and/or . /// private readonly HashSet _projectsWithErrors = []; - /// - /// Last project for which build reported an error through one of the methods. - /// - private ProjectId? _lastProjectWithReportedErrors; - /// /// Counter to help order the diagnostics in error list based on the order in which they were reported during build. /// @@ -721,7 +626,7 @@ public InProgressState(ExternalErrorDiagnosticUpdateSource owner, Solution solut private static ImmutableHashSet GetOrCreateDiagnosticIds( ProjectId projectId, Dictionary> diagnosticIdMap, - Func> computeDiagosticIds) + Func> computeDiagnosticIds) { lock (diagnosticIdMap) { @@ -731,7 +636,7 @@ private static ImmutableHashSet GetOrCreateDiagnosticIds( } } - var computedIds = computeDiagosticIds(); + var computedIds = computeDiagnosticIds(); lock (diagnosticIdMap) { @@ -781,30 +686,18 @@ public void MarkErrorsCleared(ProjectId projectId) public bool WereProjectErrorsCleared(ProjectId projectId) => _projectsWithErrorsCleared.Contains(projectId); - public void MarkLiveErrorsReported(ProjectId projectId) - => _projectsWithAllLiveErrorsReported.Add(projectId); - - public ProjectId? TryGetLastProjectWithReportedErrors() - => _lastProjectWithReportedErrors; - - public (ImmutableArray allLiveErrors, ProjectErrorMap pendingLiveErrorsToSync) GetLiveErrors() + public ImmutableArray GetLiveErrors() { var allLiveErrorsBuilder = ImmutableArray.CreateBuilder(); - var pendingLiveErrorsToSyncBuilder = ImmutableDictionary.CreateBuilder>(); foreach (var projectId in GetProjectsWithErrors()) { CancellationToken.ThrowIfCancellationRequested(); var errors = GetLiveErrorsForProject(projectId); allLiveErrorsBuilder.AddRange(errors); - - if (!_projectsWithAllLiveErrorsReported.Contains(projectId)) - { - pendingLiveErrorsToSyncBuilder.Add(projectId, errors); - } } - return (allLiveErrorsBuilder.ToImmutable(), pendingLiveErrorsToSyncBuilder.ToImmutable()); + return allLiveErrorsBuilder.ToImmutableAndClear(); // Local functions. IEnumerable GetProjectsWithErrors() @@ -835,7 +728,7 @@ public ImmutableArray GetLiveErrorsForProject(ProjectId projectI } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } public void AddErrors(DocumentId key, HashSet diagnostics) @@ -983,18 +876,10 @@ void RecordProjectContainsErrors() RoslynDebug.Assert(key is DocumentId or ProjectId); var projectId = (key is DocumentId documentId) ? documentId.ProjectId : (ProjectId)(object)key; - // New errors reported for project, need to refresh live errors. - _projectsWithAllLiveErrorsReported.Remove(projectId); - if (!_projectsWithErrors.Add(projectId)) - { return; - } - // this will make build only error list to be updated per project rather than per solution. - // basically this will make errors up to last project to show up in error list - _lastProjectWithReportedErrors = projectId; - _owner.OnBuildProgressChanged(this, BuildProgress.Updated); + _owner.OnBuildProgressChanged(this); } } @@ -1003,25 +888,6 @@ private static Dictionary GetErrorSet(Dictionary map.GetOrAdd(key, _ => new Dictionary(DiagnosticDataComparer.Instance)); } - private sealed class ArgumentKey : BuildToolId.Base - { - public ArgumentKey(object? key) : base(key) - { - } - - public override string BuildTool - { - get { return PredefinedBuildTools.Build; } - } - - public override bool Equals(object? obj) - => obj is ArgumentKey && - base.Equals(obj); - - public override int GetHashCode() - => base.GetHashCode(); - } - private sealed class DiagnosticDataComparer : IEqualityComparer { public static readonly DiagnosticDataComparer Instance = new(); diff --git a/src/VisualStudio/Core/Def/TaskList/HostDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/TaskList/HostDiagnosticUpdateSource.cs index 5cac4a34d944e..3f20d78d8cc85 100644 --- a/src/VisualStudio/Core/Def/TaskList/HostDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/TaskList/HostDiagnosticUpdateSource.cs @@ -2,147 +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. -#nullable disable - -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; -// exporting both Abstract and HostDiagnosticUpdateSource is just to make testing easier. -// use HostDiagnosticUpdateSource when abstract one is not needed for testing purpose -[Export(typeof(AbstractHostDiagnosticUpdateSource))] -[Export(typeof(HostDiagnosticUpdateSource))] -internal sealed class HostDiagnosticUpdateSource : AbstractHostDiagnosticUpdateSource, IProjectSystemDiagnosticSource +internal sealed class HostDiagnosticUpdateSource : IProjectSystemDiagnosticSource { - private readonly Lazy _workspace; - - private readonly object _gate = new(); - private readonly Dictionary> _diagnosticMap = []; - - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public HostDiagnosticUpdateSource(Lazy workspace, IDiagnosticUpdateSourceRegistrationService registrationService) - { - _workspace = workspace; - - registrationService.Register(this); - } - - public override Workspace Workspace - { - get - { - return _workspace.Value; - } - } - - private void AddDiagnosticsCreatedArgsForProject(ref TemporaryArray builder, ProjectId projectId, object key, IEnumerable items) - { - var args = DiagnosticsUpdatedArgs.DiagnosticsCreated( - CreateId(projectId, key), - Workspace, - solution: null, - projectId: projectId, - documentId: null, - diagnostics: items.AsImmutableOrEmpty()); - - builder.Add(args); - } - - private void AddDiagnosticsRemovedArgsForProject(ref TemporaryArray builder, ProjectId projectId, object key) - { - var args = DiagnosticsUpdatedArgs.DiagnosticsRemoved( - CreateId(projectId, key), - Workspace, - solution: null, - projectId: projectId, - documentId: null); - - builder.Add(args); - } - - private object CreateId(ProjectId projectId, object key) => Tuple.Create(this, projectId, key); - - public void UpdateAndAddDiagnosticsArgsForProject(ref TemporaryArray builder, ProjectId projectId, object key, IEnumerable items) - { - Contract.ThrowIfNull(projectId); - Contract.ThrowIfNull(key); - Contract.ThrowIfNull(items); - - lock (_gate) - { - _diagnosticMap.GetOrAdd(projectId, id => new HashSet()).Add(key); - } - - AddDiagnosticsCreatedArgsForProject(ref builder, projectId, key, items); - } - - void IProjectSystemDiagnosticSource.UpdateDiagnosticsForProject(ProjectId projectId, object key, IEnumerable items) - { - using var argsBuilder = TemporaryArray.Empty; - UpdateAndAddDiagnosticsArgsForProject(ref argsBuilder.AsRef(), projectId, key, items); - RaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - } - - void IProjectSystemDiagnosticSource.ClearAllDiagnosticsForProject(ProjectId projectId) - { - Contract.ThrowIfNull(projectId); - - HashSet projectDiagnosticKeys; - lock (_gate) - { - if (_diagnosticMap.TryGetValue(projectId, out projectDiagnosticKeys)) - { - _diagnosticMap.Remove(projectId); - } - } - - using var argsBuilder = TemporaryArray.Empty; - if (projectDiagnosticKeys != null) - { - foreach (var key in projectDiagnosticKeys) - { - AddDiagnosticsRemovedArgsForProject(ref argsBuilder.AsRef(), projectId, key); - } - } - - AddArgsToClearAnalyzerDiagnostics(ref argsBuilder.AsRef(), projectId); - RaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); - } - - internal void ClearAndAddDiagnosticsArgsForProject(ref TemporaryArray builder, ProjectId projectId, object key) - { - Contract.ThrowIfNull(projectId); - Contract.ThrowIfNull(key); - - var raiseEvent = false; - lock (_gate) - { - if (_diagnosticMap.TryGetValue(projectId, out var projectDiagnosticKeys)) - { - raiseEvent = projectDiagnosticKeys.Remove(key); - } - } - - if (raiseEvent) - { - AddDiagnosticsRemovedArgsForProject(ref builder, projectId, key); - } - } + public static readonly HostDiagnosticUpdateSource Instance = new(); - void IProjectSystemDiagnosticSource.ClearDiagnosticsForProject(ProjectId projectId, object key) + private HostDiagnosticUpdateSource() { - using var argsBuilder = TemporaryArray.Empty; - ClearAndAddDiagnosticsArgsForProject(ref argsBuilder.AsRef(), projectId, key); - RaiseDiagnosticsUpdated(argsBuilder.ToImmutableAndClear()); } public DiagnosticData CreateAnalyzerLoadFailureDiagnostic(AnalyzerLoadFailureEventArgs e, string fullPath, ProjectId projectId, string language) diff --git a/src/VisualStudio/Core/Def/Telemetry/Shared/AggregatingTelemetryLog.cs b/src/VisualStudio/Core/Def/Telemetry/Shared/AggregatingTelemetryLog.cs index 042a8bb5877a1..711cf06437e77 100644 --- a/src/VisualStudio/Core/Def/Telemetry/Shared/AggregatingTelemetryLog.cs +++ b/src/VisualStudio/Core/Def/Telemetry/Shared/AggregatingTelemetryLog.cs @@ -28,7 +28,6 @@ internal sealed class AggregatingTelemetryLog : ITelemetryLog private readonly HistogramConfiguration? _histogramConfiguration; private readonly string _eventName; private readonly FunctionId _functionId; - private readonly AggregatingTelemetryLogManager _aggregatingTelemetryLogManager; private readonly object _flushLock; private ImmutableDictionary Histogram, TelemetryEvent TelemetryEvent, object Lock)> _histograms = ImmutableDictionary, TelemetryEvent, object)>.Empty; @@ -40,7 +39,7 @@ internal sealed class AggregatingTelemetryLog : ITelemetryLog /// Used to derive meter name /// Optional values indicating bucket boundaries in milliseconds. If not specified, /// all histograms created will use the default histogram configuration - public AggregatingTelemetryLog(TelemetrySession session, FunctionId functionId, double[]? bucketBoundaries, AggregatingTelemetryLogManager aggregatingTelemetryLogManager) + public AggregatingTelemetryLog(TelemetrySession session, FunctionId functionId, double[]? bucketBoundaries) { var meterName = TelemetryLogger.GetPropertyName(functionId, "meter"); var meterProvider = new VSTelemetryMeterProvider(); @@ -49,7 +48,6 @@ public AggregatingTelemetryLog(TelemetrySession session, FunctionId functionId, _meter = meterProvider.CreateMeter(meterName, version: MeterVersion); _eventName = TelemetryLogger.GetEventName(functionId); _functionId = functionId; - _aggregatingTelemetryLogManager = aggregatingTelemetryLogManager; _flushLock = new(); if (bucketBoundaries != null) @@ -104,8 +102,6 @@ public void Log(KeyValueLogMessage logMessage) { histogram.Record(value); } - - _aggregatingTelemetryLogManager.EnsureTelemetryWorkQueued(); } public IDisposable? LogBlockTime(KeyValueLogMessage logMessage, int minThresholdMs) diff --git a/src/VisualStudio/Core/Def/Telemetry/Shared/AggregatingTelemetryLogManager.cs b/src/VisualStudio/Core/Def/Telemetry/Shared/AggregatingTelemetryLogManager.cs index 1188da45610ee..2b3042e607592 100644 --- a/src/VisualStudio/Core/Def/Telemetry/Shared/AggregatingTelemetryLogManager.cs +++ b/src/VisualStudio/Core/Def/Telemetry/Shared/AggregatingTelemetryLogManager.cs @@ -2,39 +2,24 @@ // The .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.Internal.Log; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Telemetry; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Telemetry; /// -/// Manages creation and obtaining aggregated telemetry logs. Also, notifies logs to -/// send aggregated events every 30 minutes. +/// Manages creation and obtaining aggregated telemetry logs. /// internal sealed class AggregatingTelemetryLogManager { - private static readonly TimeSpan s_batchedTelemetryCollectionPeriod = TimeSpan.FromMinutes(30); - private readonly TelemetrySession _session; - private readonly AsyncBatchingWorkQueue _postTelemetryQueue; private ImmutableDictionary _aggregatingLogs = ImmutableDictionary.Empty; - public AggregatingTelemetryLogManager(TelemetrySession session, IAsynchronousOperationListener asyncListener) + public AggregatingTelemetryLogManager(TelemetrySession session) { _session = session; - - _postTelemetryQueue = new AsyncBatchingWorkQueue( - s_batchedTelemetryCollectionPeriod, - PostCollectedTelemetryAsync, - asyncListener, - CancellationToken.None); } public ITelemetryLog? GetLog(FunctionId functionId, double[]? bucketBoundaries) @@ -42,22 +27,11 @@ public AggregatingTelemetryLogManager(TelemetrySession session, IAsynchronousOpe if (!_session.IsOptedIn) return null; - return ImmutableInterlocked.GetOrAdd(ref _aggregatingLogs, functionId, functionId => new AggregatingTelemetryLog(_session, functionId, bucketBoundaries, this)); - } - - public void EnsureTelemetryWorkQueued() - { - // Ensure PostCollectedTelemetryAsync will get fired after the collection period. - _postTelemetryQueue.AddWork(); - } - - private ValueTask PostCollectedTelemetryAsync(CancellationToken token) - { - token.ThrowIfCancellationRequested(); - - Flush(); - - return ValueTaskFactory.CompletedTask; + return ImmutableInterlocked.GetOrAdd( + ref _aggregatingLogs, + functionId, + static (functionId, arg) => new AggregatingTelemetryLog(arg._session, functionId, arg.bucketBoundaries), + factoryArgument: (_session, bucketBoundaries)); } public void Flush() diff --git a/src/VisualStudio/Core/Def/Telemetry/Shared/TelemetryLogProvider.cs b/src/VisualStudio/Core/Def/Telemetry/Shared/TelemetryLogProvider.cs index 3bc330135068c..16e44077e4178 100644 --- a/src/VisualStudio/Core/Def/Telemetry/Shared/TelemetryLogProvider.cs +++ b/src/VisualStudio/Core/Def/Telemetry/Shared/TelemetryLogProvider.cs @@ -17,17 +17,17 @@ internal sealed class TelemetryLogProvider : ITelemetryLogProvider private readonly AggregatingTelemetryLogManager _aggregatingTelemetryLogManager; private readonly VisualStudioTelemetryLogManager _visualStudioTelemetryLogManager; - private TelemetryLogProvider(TelemetrySession session, ILogger telemetryLogger, IAsynchronousOperationListener asyncListener) + private TelemetryLogProvider(TelemetrySession session, ILogger telemetryLogger) { - _aggregatingTelemetryLogManager = new AggregatingTelemetryLogManager(session, asyncListener); + _aggregatingTelemetryLogManager = new AggregatingTelemetryLogManager(session); _visualStudioTelemetryLogManager = new VisualStudioTelemetryLogManager(session, telemetryLogger); } public static TelemetryLogProvider Create(TelemetrySession session, ILogger telemetryLogger, IAsynchronousOperationListener asyncListener) { - var logProvider = new TelemetryLogProvider(session, telemetryLogger, asyncListener); + var logProvider = new TelemetryLogProvider(session, telemetryLogger); - TelemetryLogging.SetLogProvider(logProvider); + TelemetryLogging.SetLogProvider(logProvider, asyncListener); return logProvider; } diff --git a/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs b/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs index ace4d7cfc5ea6..e15e7d56ecbc2 100644 --- a/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs +++ b/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs @@ -40,7 +40,7 @@ public IWpfTableControl4 CreateTableControl() _tableManager, autoSubscribe: true, BuildColumnStates(), - UnusedReferencesColumnDefinitions.ColumnNames.ToArray()); + [.. UnusedReferencesColumnDefinitions.ColumnNames]); tableControl.ShowGroupingLine = true; tableControl.DoColumnsAutoAdjust = true; tableControl.DoSortingAndGroupingWhileUnstable = true; diff --git a/src/VisualStudio/Core/Def/Utilities/VsCodeWindowViewTracker.cs b/src/VisualStudio/Core/Def/Utilities/VsCodeWindowViewTracker.cs index 9c28b1003b798..5f98a5145219a 100644 --- a/src/VisualStudio/Core/Def/Utilities/VsCodeWindowViewTracker.cs +++ b/src/VisualStudio/Core/Def/Utilities/VsCodeWindowViewTracker.cs @@ -6,10 +6,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.VisualStudio.Debugger.ComponentInterfaces; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation; using Microsoft.VisualStudio.Text.Editor; @@ -26,9 +24,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Utilities; /// /// All members of this class are UI thread affinitized, including the constructor. /// -internal sealed class VsCodeWindowViewTracker : ForegroundThreadAffinitizedObject, IDisposable, IVsCodeWindowEvents +internal sealed class VsCodeWindowViewTracker : IDisposable, IVsCodeWindowEvents { private readonly IVsCodeWindow _codeWindow; + private readonly IThreadingContext _threadingContext; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; private readonly ComEventSink _codeWindowEventsSink; @@ -39,11 +38,13 @@ internal sealed class VsCodeWindowViewTracker : ForegroundThreadAffinitizedObjec private readonly Dictionary _trackedTextViews = []; public VsCodeWindowViewTracker(IVsCodeWindow codeWindow, IThreadingContext threadingContext, IVsEditorAdaptersFactoryService editorAdaptersFactoryService) - : base(threadingContext, assertIsForeground: true) { _codeWindow = codeWindow; + _threadingContext = threadingContext; _editorAdaptersFactoryService = editorAdaptersFactoryService; + _threadingContext.ThrowIfNotOnUIThread(); + _codeWindowEventsSink = ComEventSink.Advise(codeWindow, this); if (ErrorHandler.Succeeded(_codeWindow.GetPrimaryView(out var pTextView)) && pTextView != null) @@ -56,7 +57,7 @@ public VsCodeWindowViewTracker(IVsCodeWindow codeWindow, IThreadingContext threa private void StartTrackingView(IVsTextView pTextView) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (!_trackedTextViews.ContainsKey(pTextView)) { @@ -73,7 +74,7 @@ private void StartTrackingView(IVsTextView pTextView) private void StopTrackingView(IVsTextView pView) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_trackedTextViews.TryGetValue(pView, out var view)) { @@ -86,7 +87,7 @@ private void StopTrackingView(IVsTextView pView) public ITextView GetActiveView() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); ErrorHandler.ThrowOnFailure(_codeWindow.GetLastActiveView(out var pView)); Contract.ThrowIfNull(pView, $"{nameof(IVsCodeWindow.GetLastActiveView)} returned success, but did not provide a view."); @@ -97,7 +98,7 @@ public ITextView GetActiveView() int IVsCodeWindowEvents.OnNewView(IVsTextView pView) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); StartTrackingView(pView); return VSConstants.S_OK; @@ -105,7 +106,7 @@ int IVsCodeWindowEvents.OnNewView(IVsTextView pView) int IVsCodeWindowEvents.OnCloseView(IVsTextView pView) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); StopTrackingView(pView); return VSConstants.S_OK; diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs index 8692768d94ba5..011c3c055f5bf 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml.cs @@ -114,7 +114,7 @@ private TreeViewItemBase GetPreviousItem() { if (ValueTrackingTreeView.SelectedItem is null) { - return (TreeViewItemBase)ValueTrackingTreeView.Items[ValueTrackingTreeView.Items.Count - 1]; + return (TreeViewItemBase)ValueTrackingTreeView.Items[^1]; } var item = (TreeViewItemBase)ValueTrackingTreeView.SelectedItem; diff --git a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs index a0475d1e3e760..114576ac819e8 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs @@ -33,13 +33,12 @@ using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; using Microsoft.VisualStudio.Text.Projection; using Roslyn.Utilities; -using static Microsoft.VisualStudio.VSConstants; using IVsContainedLanguageHost = Microsoft.VisualStudio.TextManager.Interop.IVsContainedLanguageHost; using IVsTextBufferCoordinator = Microsoft.VisualStudio.TextManager.Interop.IVsTextBufferCoordinator; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus; -internal sealed partial class ContainedDocument : ForegroundThreadAffinitizedObject, IContainedDocument +internal sealed partial class ContainedDocument : IContainedDocument { private const string ReturnReplacementString = @"{|r|}"; private const string NewLineReplacementString = @"{|n|}"; @@ -99,7 +98,6 @@ public static ContainedDocument TryGetContainedDocument(DocumentId id) public IVsContainedLanguageHost ContainedLanguageHost { get; set; } public ContainedDocument( - IThreadingContext threadingContext, DocumentId documentId, ITextBuffer subjectBuffer, ITextBuffer dataBuffer, @@ -108,7 +106,6 @@ public ContainedDocument( ProjectSystemProject project, IComponentModel componentModel, AbstractFormattingRule vbHelperFormattingRule) - : base(threadingContext) { _componentModel = componentModel; _workspace = workspace; @@ -714,7 +711,7 @@ public IEnumerable GetEditorVisibleSpans() } else { - return SpecializedCollections.EmptyEnumerable(); + return []; } } @@ -1073,7 +1070,7 @@ private bool IsCodeBlock(ITextSnapshot surfaceSnapshot, int position, char ch) private static bool CheckCode(ITextSnapshot snapshot, int position, char ch, string tag, bool checkAt = true) { - if (ch != tag[tag.Length - 1] || position < tag.Length) + if (ch != tag[^1] || position < tag.Length) { return false; } diff --git a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.IVsContainedCode.cs b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.IVsContainedCode.cs index f8c1395195ae9..5d4229288df0a 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.IVsContainedCode.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.IVsContainedCode.cs @@ -52,9 +52,7 @@ private IList EnumOriginalCodeBlocksWorker(CancellationToken var snapshot = this.SubjectBuffer.CurrentSnapshot; var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) - { - return SpecializedCollections.EmptyList(); - } + return []; return document.GetVisibleCodeBlocks(cancellationToken) .Select(tuple => new TextSpanAndCookie diff --git a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs index dbb65c0e55adf..639f135bb5bde 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs @@ -126,7 +126,6 @@ internal ContainedLanguage( } ContainedDocument = new ContainedDocument( - ComponentModel.GetService(), documentId, subjectBuffer: SubjectBuffer, dataBuffer: DataBuffer, @@ -171,7 +170,7 @@ private void OnDataBufferChanged(object sender, TextContentChangedEventArgs e) { // we don't actually care what has changed in primary buffer. we just want to re-analyze secondary buffer // when primary buffer has changed to update diagnostic positions. - _diagnosticAnalyzerService.Reanalyze(this.Workspace, projectIds: null, documentIds: SpecializedCollections.SingletonEnumerable(this.ContainedDocument.Id), highPriority: false); + _diagnosticAnalyzerService.RequestDiagnosticRefresh(); } public string GetFilePathFromBuffers() diff --git a/src/VisualStudio/Core/Def/Watson/FaultReporter.cs b/src/VisualStudio/Core/Def/Watson/FaultReporter.cs index 4914884a760df..d6ba325df20ef 100644 --- a/src/VisualStudio/Core/Def/Watson/FaultReporter.cs +++ b/src/VisualStudio/Core/Def/Watson/FaultReporter.cs @@ -21,6 +21,13 @@ namespace Microsoft.CodeAnalysis.ErrorReporting; internal static class FaultReporter { + /// + /// We can no longer use the common fault description property as it has to be suppressed due to poisoned data in past releases. + /// This means that prism will no longer show the fault description either. We'll store the clean description in a custom + /// property so we can access it manually if needed. + /// + private const string CustomFaultDescriptionPropertyName = "roslyn.fault.description"; + private static readonly object _guard = new(); private static ImmutableArray s_telemetrySessions = []; private static ImmutableArray s_loggers = []; @@ -130,9 +137,10 @@ public static void ReportFault(Exception exception, FaultSeverity severity, bool logger.TraceEvent(TraceEventType.Error, 1, logMessage); } + var description = GetDescription(exception); var faultEvent = new FaultEvent( eventName: TelemetryLogger.GetEventName(FunctionId.NonFatalWatson), - description: GetDescription(exception), + description: description, severity, exceptionObject: exception, gatherEventDetails: faultUtility => @@ -167,6 +175,8 @@ public static void ReportFault(Exception exception, FaultSeverity severity, bool return 0; }); + faultEvent.Properties[CustomFaultDescriptionPropertyName] = description; + foreach (var session in s_telemetrySessions) { session.PostEvent(faultEvent); @@ -252,8 +262,9 @@ private static string GetDescription(Exception exception) { } - // If we couldn't get a stack, do this - return exception.Message; + // If we couldn't get a stack, report a generic message. + // The exception message is already reported in a separate cred-scanned property. + return "Roslyn NonFatal Watson"; } private static IList CollectLogHubFilePaths() @@ -269,7 +280,7 @@ private static IList CollectLogHubFilePaths() // ignore failures } - return SpecializedCollections.EmptyList(); + return []; } private static IList CollectServiceHubLogFilePaths() @@ -291,7 +302,7 @@ private static IList CollectServiceHubLogFilePaths() // ignore failures } - return SpecializedCollections.EmptyList(); + return []; } private static List CollectFilePaths(string logDirectoryPath, string logFileExtension, Func shouldExcludeLogFile) diff --git a/src/VisualStudio/Core/Def/Workspace/GlobalUndoServiceFactory.WorkspaceGlobalUndoTransaction.cs b/src/VisualStudio/Core/Def/Workspace/GlobalUndoServiceFactory.WorkspaceGlobalUndoTransaction.cs index a3fc41cc06e48..3b12e746f5205 100644 --- a/src/VisualStudio/Core/Def/Workspace/GlobalUndoServiceFactory.WorkspaceGlobalUndoTransaction.cs +++ b/src/VisualStudio/Core/Def/Workspace/GlobalUndoServiceFactory.WorkspaceGlobalUndoTransaction.cs @@ -9,11 +9,10 @@ using System.Runtime.InteropServices; using System.Threading; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Undo; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.TextManager.Interop; using Roslyn.Utilities; @@ -24,8 +23,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; internal partial class GlobalUndoServiceFactory { - private class WorkspaceUndoTransaction : ForegroundThreadAffinitizedObject, IWorkspaceGlobalUndoTransaction + private sealed class WorkspaceUndoTransaction : IWorkspaceGlobalUndoTransaction { + private readonly IThreadingContext _threadingContext; private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; private readonly IVsLinkedUndoTransactionManager _undoManager; private readonly Workspace _workspace; @@ -42,14 +42,16 @@ public WorkspaceUndoTransaction( Workspace workspace, string description, GlobalUndoService service) - : base(threadingContext, assertIsForeground: true) { + _threadingContext = threadingContext; _undoHistoryRegistry = undoHistoryRegistry; _undoManager = undoManager; _workspace = workspace; _description = description; _service = service; + _threadingContext.ThrowIfNotOnUIThread(); + Marshal.ThrowExceptionForHR(_undoManager.OpenLinkedUndo((uint)LinkedTransactionFlags2.mdtGlobal, _description)); _transactionAlive = true; } @@ -89,7 +91,7 @@ public void AddDocument(DocumentId id) public void Commit() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); // once either commit or disposed is called, don't do finalizer check GC.SuppressFinalize(this); @@ -113,7 +115,7 @@ public void Commit() public void Dispose() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); // once either commit or disposed is called, don't do finalizer check GC.SuppressFinalize(this); diff --git a/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs index f02492da4e0da..1bb5c963478d4 100644 --- a/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs @@ -10,7 +10,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -30,19 +32,24 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; +using InfoBarInfo = (ImageMoniker imageMoniker, string message); + /// /// Provides the support for opening files pointing to source generated documents, and keeping the content updated accordingly. /// [Export(typeof(SourceGeneratedFileManager))] internal sealed class SourceGeneratedFileManager : IOpenTextBufferEventListener { - private readonly IServiceProvider _serviceProvider; + private readonly SVsServiceProvider _serviceProvider; + private readonly IVsService _vsInfoBarUIFactory; + private readonly IVsService _vsShell; private readonly IThreadingContext _threadingContext; - private readonly ForegroundThreadAffinitizedObject _foregroundThreadAffinitizedObject; - private readonly IAsynchronousOperationListener _listener; private readonly ITextDocumentFactoryService _textDocumentFactoryService; private readonly VisualStudioDocumentNavigationService _visualStudioDocumentNavigationService; + private readonly IAsynchronousOperationListener _listener; + private readonly IAsynchronousOperationListenerProvider _listenerProvider; + /// /// The temporary directory that we'll create file names under to act as a prefix we can later recognize and use. /// @@ -67,7 +74,9 @@ internal sealed class SourceGeneratedFileManager : IOpenTextBufferEventListener [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public SourceGeneratedFileManager( - [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, + SVsServiceProvider serviceProvider, + IVsService vsInfoBarUIFactory, + IVsService vsShell, IThreadingContext threadingContext, OpenTextBufferProvider openTextBufferProvider, IVsEditorAdaptersFactoryService editorAdaptersFactoryService, @@ -77,17 +86,19 @@ public SourceGeneratedFileManager( IAsynchronousOperationListenerProvider listenerProvider) { _serviceProvider = serviceProvider; + _vsInfoBarUIFactory = vsInfoBarUIFactory; + _vsShell = vsShell; _threadingContext = threadingContext; - _foregroundThreadAffinitizedObject = new ForegroundThreadAffinitizedObject(threadingContext, assertIsForeground: false); _textDocumentFactoryService = textDocumentFactoryService; _temporaryDirectory = PathUtilities.EnsureTrailingSeparator(Path.Combine(Path.GetTempPath(), "VSGeneratedDocuments")); _visualStudioWorkspace = visualStudioWorkspace; _visualStudioDocumentNavigationService = visualStudioDocumentNavigationService; - Directory.CreateDirectory(_temporaryDirectory); - + _listenerProvider = listenerProvider; _listener = listenerProvider.GetListener(FeatureAttribute.SourceGenerators); + Directory.CreateDirectory(_temporaryDirectory); + openTextBufferProvider.AddListener(this); } @@ -146,7 +157,7 @@ public bool TryGetGeneratedFileInformation( string filePath, out SourceGeneratedDocumentIdentity identity) { - _foregroundThreadAffinitizedObject.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); identity = default; @@ -176,14 +187,14 @@ public bool TryGetGeneratedFileInformation( void IOpenTextBufferEventListener.OnOpenDocument(string moniker, ITextBuffer textBuffer, IVsHierarchy? hierarchy) { - _foregroundThreadAffinitizedObject.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (TryGetGeneratedFileInformation(moniker, out var documentIdentity)) { // Attach to the text buffer if we haven't already if (!_openFiles.TryGetValue(moniker, out var openFile)) { - openFile = new OpenSourceGeneratedFile(this, textBuffer, _visualStudioWorkspace, documentIdentity, _threadingContext); + openFile = new OpenSourceGeneratedFile(this, textBuffer, documentIdentity); _openFiles.Add(moniker, openFile); _threadingContext.JoinableTaskFactory.Run(() => openFile.RefreshFileAsync(CancellationToken.None).AsTask()); @@ -199,14 +210,12 @@ void IOpenTextBufferEventListener.OnOpenDocument(string moniker, ITextBuffer tex void IOpenTextBufferEventListener.OnDocumentOpenedIntoWindowFrame(string moniker, IVsWindowFrame windowFrame) { if (_openFiles.TryGetValue(moniker, out var openFile)) - { - openFile.SetWindowFrame(windowFrame); - } + _threadingContext.JoinableTaskFactory.Run(() => openFile.SetWindowFrameAsync(windowFrame)); } void IOpenTextBufferEventListener.OnCloseDocument(string moniker) { - _foregroundThreadAffinitizedObject.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_openFiles.TryGetValue(moniker, out var openFile)) { @@ -223,11 +232,10 @@ void IOpenTextBufferEventListener.OnRenameDocument(string newMoniker, string old { } - private class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject, IDisposable + private sealed class OpenSourceGeneratedFile : IDisposable { private readonly SourceGeneratedFileManager _fileManager; private readonly ITextBuffer _textBuffer; - private readonly Workspace _workspace; private readonly SourceGeneratedDocumentIdentity _documentIdentity; private readonly IWorkspaceConfigurationService? _workspaceConfigurationService; @@ -250,25 +258,21 @@ private class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject, IDisp private readonly AsyncBatchingWorkQueue _batchingWorkQueue; /// - /// The of the active window. This may be null if we're in the middle of construction and - /// we haven't been given it yet. + /// The info bar of the active window. This may be null if we're in the middle of construction and we haven't + /// created it yet /// - private IVsWindowFrame? _windowFrame; + private VisualStudioInfoBar? _infoBar; + private VisualStudioInfoBar.InfoBarMessage? _currentInfoBarMessage; - private string? _windowFrameMessageToShow = null; - private ImageMoniker _windowFrameImageMonikerToShow = default; - private string? _currentWindowFrameMessage = null; - private ImageMoniker _currentWindowFrameImageMoniker = default; - private IVsInfoBarUIElement? _currentWindowFrameInfoBarElement = null; + private InfoBarInfo? _infoToShow = null; - public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuffer textBuffer, Workspace workspace, SourceGeneratedDocumentIdentity documentIdentity, IThreadingContext threadingContext) - : base(threadingContext, assertIsForeground: true) + public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuffer textBuffer, SourceGeneratedDocumentIdentity documentIdentity) { + fileManager._threadingContext.ThrowIfNotOnUIThread(); _fileManager = fileManager; _textBuffer = textBuffer; - _workspace = workspace; _documentIdentity = documentIdentity; - _workspaceConfigurationService = _workspace.Services.GetService(); + _workspaceConfigurationService = this.Workspace.Services.GetService(); // 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. @@ -283,7 +287,7 @@ public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuff readOnlyRegionEdit.Apply(); } - _workspace.WorkspaceChanged += OnWorkspaceChanged; + this.Workspace.WorkspaceChanged += OnWorkspaceChanged; _batchingWorkQueue = new AsyncBatchingWorkQueue( TimeSpan.FromSeconds(1), @@ -292,23 +296,25 @@ public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuff _cancellationTokenSource.Token); } + private Workspace Workspace => _fileManager._visualStudioWorkspace; + private void DisconnectFromWorkspaceIfOpen() { - AssertIsForeground(); + _fileManager._threadingContext.ThrowIfNotOnUIThread(); - if (_workspace.IsDocumentOpen(_documentIdentity.DocumentId)) + if (this.Workspace.IsDocumentOpen(_documentIdentity.DocumentId)) { var sourceGeneratedDocument = (SourceGeneratedDocument?)_textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); Contract.ThrowIfNull(sourceGeneratedDocument); - _workspace.OnSourceGeneratedDocumentClosed(sourceGeneratedDocument); + this.Workspace.OnSourceGeneratedDocumentClosed(sourceGeneratedDocument); } } public void Dispose() { - AssertIsForeground(); + _fileManager._threadingContext.ThrowIfNotOnUIThread(); - _workspace.WorkspaceChanged -= OnWorkspaceChanged; + this.Workspace.WorkspaceChanged -= OnWorkspaceChanged; // Disconnect the buffer from the workspace before making it eligible for edits DisconnectFromWorkspaceIfOpen(); @@ -329,25 +335,25 @@ public async ValueTask RefreshFileAsync(CancellationToken cancellationToken) { SourceGeneratedDocument? generatedDocument = null; SourceText? generatedSource = null; - var project = _workspace.CurrentSolution.GetProject(_documentIdentity.DocumentId.ProjectId); + var project = this.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. - string? windowFrameMessageToShow; - ImageMoniker windowFrameImageMonikerToShow; + InfoBarInfo infoToShow; if (project == null) { - windowFrameMessageToShow = "The project no longer exists."; - windowFrameImageMonikerToShow = KnownMonikers.StatusError; + infoToShow = (KnownMonikers.StatusError, ServicesVSResources.The_project_no_longer_exists); } else { generatedDocument = await project.GetSourceGeneratedDocumentAsync(_documentIdentity.DocumentId, cancellationToken).ConfigureAwait(false); if (generatedDocument != null) { - windowFrameMessageToShow = string.Format(ServicesVSResources.This_file_is_autogenerated_by_0_and_cannot_be_edited, GeneratorDisplayName); - windowFrameImageMonikerToShow = default; + infoToShow = (imageMoniker: default, string.Format( + ServicesVSResources.This_file_was_generated_by_0_at_1_and_cannot_be_edited, + GeneratorDisplayName, + generatedDocument.GenerationDateTime.ToLocalTime())); generatedSource = await generatedDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); } else @@ -355,21 +361,18 @@ public async ValueTask RefreshFileAsync(CancellationToken cancellationToken) // The file isn't there anymore; do we still have the generator at all? if (project.AnalyzerReferences.Any(a => a.FullPath == _documentIdentity.Generator.AssemblyPath)) { - windowFrameMessageToShow = string.Format(ServicesVSResources.The_generator_0_that_generated_this_file_has_stopped_generating_this_file, GeneratorDisplayName); - windowFrameImageMonikerToShow = KnownMonikers.StatusError; + infoToShow = (KnownMonikers.StatusError, string.Format(ServicesVSResources.The_generator_0_that_generated_this_file_has_stopped_generating_this_file, GeneratorDisplayName)); } else { - windowFrameMessageToShow = string.Format(ServicesVSResources.The_generator_0_that_generated_this_file_has_been_removed_from_the_project, GeneratorDisplayName); - windowFrameImageMonikerToShow = KnownMonikers.StatusError; + infoToShow = (KnownMonikers.StatusError, string.Format(ServicesVSResources.The_generator_0_that_generated_this_file_has_been_removed_from_the_project, GeneratorDisplayName)); } } } - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _fileManager._threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - _windowFrameMessageToShow = windowFrameMessageToShow; - _windowFrameImageMonikerToShow = windowFrameImageMonikerToShow; + _infoToShow = infoToShow; // Update the text if we have new text if (generatedSource != null) @@ -404,9 +407,9 @@ public async ValueTask RefreshFileAsync(CancellationToken cancellationToken) // if the file is repeatedly appearing and disappearing. var connectToWorkspace = _workspaceConfigurationService?.Options.EnableOpeningSourceGeneratedFiles != false; - if (connectToWorkspace && !_workspace.IsDocumentOpen(_documentIdentity.DocumentId)) + if (connectToWorkspace && !this.Workspace.IsDocumentOpen(_documentIdentity.DocumentId)) { - _workspace.OnSourceGeneratedDocumentOpened(_textBuffer.AsTextContainer(), generatedDocument); + this.Workspace.OnSourceGeneratedDocumentOpened(_textBuffer.AsTextContainer(), generatedDocument); } } finally @@ -423,87 +426,110 @@ public async ValueTask RefreshFileAsync(CancellationToken cancellationToken) } // Update the InfoBar either way - EnsureWindowFrameInfoBarUpdated(); + await EnsureWindowFrameInfoBarUpdatedAsync(cancellationToken).ConfigureAwait(true); } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) { - var oldProject = e.OldSolution.GetProject(_documentIdentity.DocumentId.ProjectId); - var newProject = e.NewSolution.GetProject(_documentIdentity.DocumentId.ProjectId); + var projectId = _documentIdentity.DocumentId.ProjectId; + + // Trivial check. see if the SG version of these projects changed. If so, we definitely want to update + // this generated file. + if (e.OldSolution.GetSourceGeneratorExecutionVersion(projectId) != + e.NewSolution.GetSourceGeneratorExecutionVersion(projectId)) + { + _batchingWorkQueue.AddWork(); + return; + } + + var oldProject = e.OldSolution.GetProject(projectId); + var newProject = e.NewSolution.GetProject(projectId); if (oldProject != null && newProject != null) { // We'll start this work asynchronously to figure out if we need to change; if the file is closed the cancellationToken // is triggered and this will no-op. - var asyncToken = _fileManager._listener.BeginAsyncOperation(nameof(OpenSourceGeneratedFile) + "." + nameof(OnWorkspaceChanged)); + var asyncToken = _fileManager._listener.BeginAsyncOperation($"{nameof(OpenSourceGeneratedFile)}.{nameof(OnWorkspaceChanged)}"); + CheckDependentVersionsAsync().CompletesAsyncOperation(asyncToken); + } + + async Task CheckDependentVersionsAsync() + { + // Ensure we do this off the thread that is telling us about workspace changes. + await Task.Yield(); - Task.Run(async () => + if (await oldProject.GetDependentVersionAsync(_cancellationTokenSource.Token).ConfigureAwait(false) != + await newProject.GetDependentVersionAsync(_cancellationTokenSource.Token).ConfigureAwait(false)) { - if (await oldProject.GetDependentVersionAsync(_cancellationTokenSource.Token).ConfigureAwait(false) != - await newProject.GetDependentVersionAsync(_cancellationTokenSource.Token).ConfigureAwait(false)) - { - _batchingWorkQueue.AddWork(); - } - }, _cancellationTokenSource.Token).CompletesAsyncOperation(asyncToken); + _batchingWorkQueue.AddWork(); + } } } - internal void SetWindowFrame(IVsWindowFrame windowFrame) + internal async Task SetWindowFrameAsync(IVsWindowFrame windowFrame) { - AssertIsForeground(); + var cancellationToken = _cancellationTokenSource.Token; + await _fileManager._threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - if (_windowFrame != null) - { - // We already have a window frame, and we don't expect to get a second one + // Only need to do this once. Can use the presence of the info bar to check this. + if (_infoBar != null) return; - } - _windowFrame = windowFrame; + _infoBar = new VisualStudioInfoBar( + _fileManager._threadingContext, _fileManager._vsInfoBarUIFactory, _fileManager._vsShell, _fileManager._listenerProvider, windowFrame); // We'll override the window frame and never show it as dirty, even if there's an underlying edit windowFrame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, false); windowFrame.SetProperty((int)__VSFPROPID5.VSFPROPID_OverrideCaption, _documentIdentity.HintName + " " + ServicesVSResources.generated_suffix); windowFrame.SetProperty((int)__VSFPROPID5.VSFPROPID_OverrideToolTip, _documentIdentity.HintName + " " + string.Format(ServicesVSResources.generated_by_0_suffix, GeneratorDisplayName)); - EnsureWindowFrameInfoBarUpdated(); + await EnsureWindowFrameInfoBarUpdatedAsync(cancellationToken).ConfigureAwait(true); } - private void EnsureWindowFrameInfoBarUpdated() + private async Task EnsureWindowFrameInfoBarUpdatedAsync(CancellationToken cancellationToken) { - AssertIsForeground(); + await _fileManager._threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - if (_windowFrameMessageToShow == null || - _windowFrame == null || - _currentWindowFrameMessage == _windowFrameMessageToShow && - !_currentWindowFrameImageMoniker.Equals(_windowFrameImageMonikerToShow)) - { - // We don't have anything to do, or anything to do yet. + // If we don't have a frame, or even a message, we can't do anything yet. + if (_infoToShow is null || _infoBar is null) return; - } - var infoBarFactory = (IVsInfoBarUIFactory)_fileManager._serviceProvider.GetService(typeof(SVsInfoBarUIFactory)); - Assumes.Present(infoBarFactory); - - if (ErrorHandler.Failed(_windowFrame.GetProperty((int)__VSFPROPID7.VSFPROPID_InfoBarHost, out var infoBarHostObject)) || - infoBarHostObject is not IVsInfoBarHost infoBarHost) + // bail out if no change is needed + var (imageMoniker, message) = _infoToShow.Value; + if (_currentInfoBarMessage != null && + _currentInfoBarMessage.Message == message && + _currentInfoBarMessage.ImageMoniker.Equals(imageMoniker)) { return; } - // Remove the existing bar - if (_currentWindowFrameInfoBarElement != null) - { - infoBarHost.RemoveInfoBar(_currentWindowFrameInfoBarElement); - } - - var infoBar = new InfoBarModel(_windowFrameMessageToShow, _windowFrameImageMonikerToShow, isCloseButtonVisible: false); - var infoBarUI = infoBarFactory.CreateInfoBar(infoBar); + // Remove the current message so it can be replaced with the new one. + _currentInfoBarMessage?.Remove(); - infoBarHost.AddInfoBar(infoBarUI); - - _currentWindowFrameMessage = _windowFrameMessageToShow; - _currentWindowFrameImageMoniker = _windowFrameImageMonikerToShow; - _currentWindowFrameInfoBarElement = infoBarUI; + // Capture the newly created message into a local. That way the "rerun generator" button callback can + // reference *exactly this* instance in order to remove it when it is clicked. + VisualStudioInfoBar.InfoBarMessage? infoBarMessage = null; + InfoBarUI[] infoBarItems = [new InfoBarUI(ServicesVSResources.Rerun_generator, InfoBarUI.UIKind.Button, () => + { + _fileManager._threadingContext.ThrowIfNotOnUIThread(); + Contract.ThrowIfNull(infoBarMessage); + infoBarMessage.Remove(); + + _currentInfoBarMessage = _fileManager._threadingContext.JoinableTaskFactory.Run(() => + _infoBar.ShowInfoBarMessageAsync( + ServicesVSResources.Generator_running, isCloseButtonVisible: false, KnownMonikers.StatusInformation)); + + // Force regeneration here. Nothing has actually changed, so the incremental generator architecture + // would normally just return the same values all over again. By forcing things, we drop the + // generator driver, which will force new files to actually be created. + this.Workspace.EnqueueUpdateSourceGeneratorVersion( + this._documentIdentity.DocumentId.ProjectId, + forceRegeneration: true); + })]; + + infoBarMessage = await _infoBar.ShowInfoBarMessageAsync( + message, isCloseButtonVisible: false, imageMoniker, infoBarItems).ConfigureAwait(true); + _currentInfoBarMessage = infoBarMessage; } public Task NavigateToSpanAsync(TextSpan sourceSpan, CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs index 71ccc2ccc14b3..73ead9d0ebedc 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs @@ -7,10 +7,8 @@ using System.ComponentModel.Composition; using System.Linq; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; @@ -31,8 +29,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; /// Can be accessed via the as a workspace service. /// [Export] -internal class VisualStudioActiveDocumentTracker : ForegroundThreadAffinitizedObject, IVsSelectionEvents +internal sealed class VisualStudioActiveDocumentTracker : IVsSelectionEvents { + private readonly IThreadingContext _threadingContext; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; /// @@ -51,12 +50,12 @@ public VisualStudioActiveDocumentTracker( IThreadingContext threadingContext, [Import(typeof(SVsServiceProvider))] IAsyncServiceProvider asyncServiceProvider, IVsEditorAdaptersFactoryService editorAdaptersFactoryService) - : base(threadingContext, assertIsForeground: false) { + _threadingContext = threadingContext; _editorAdaptersFactoryService = editorAdaptersFactoryService; - ThreadingContext.RunWithShutdownBlockAsync(async cancellationToken => + _threadingContext.RunWithShutdownBlockAsync(async cancellationToken => { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var monitorSelectionService = (IVsMonitorSelection?)await asyncServiceProvider.GetServiceAsync(typeof(SVsShellMonitorSelection)).ConfigureAwait(true); Assumes.Present(monitorSelectionService); @@ -82,17 +81,12 @@ public VisualStudioActiveDocumentTracker( /// public event EventHandler? DocumentsChanged; - /// - /// Raised when a non-Roslyn text buffer is edited, which can be used to back off of expensive background processing. May be raised on any thread. - /// - public event EventHandler? NonRoslynBufferTextChanged; - /// /// Returns the of the active document in a given . /// public DocumentId? TryGetActiveDocument(Workspace workspace) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); // Fetch both fields locally. If there's a write between these, that's fine -- it might mean we // don't return the DocumentId for something we could have if _activeFrame isn't listed in _visibleFrames. @@ -122,7 +116,7 @@ public VisualStudioActiveDocumentTracker( /// public ImmutableArray GetVisibleDocuments(Workspace workspace) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); var visibleFramesSnapshot = _visibleFrames; @@ -143,7 +137,7 @@ public ImmutableArray GetVisibleDocuments(Workspace workspace) public void TrackNewActiveWindowFrame(IVsWindowFrame frame) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); Contract.ThrowIfNull(frame); @@ -168,7 +162,7 @@ public void TrackNewActiveWindowFrame(IVsWindowFrame frame) private void RemoveFrame(FrameListener frame) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (frame.Frame == _activeFrame) { @@ -185,7 +179,7 @@ int IVsSelectionEvents.OnSelectionChanged(IVsHierarchy pHierOld, [ComAliasName(" int IVsSelectionEvents.OnElementValueChanged([ComAliasName("Microsoft.VisualStudio.Shell.Interop.VSSELELEMID")] uint elementid, object varValueOld, object varValueNew) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); // Process and track newly active document frame. // Note that sometimes we receive 'SEID_WindowFrame' instead of 'SEID_DocumentFrame' @@ -227,7 +221,7 @@ public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame f { _documentTracker = service; - _documentTracker.AssertIsForeground(); + _documentTracker._threadingContext.ThrowIfNotOnUIThread(); this.Frame = frame; ((IVsWindowFrame2)frame).Advise(this, out _frameEventsCookie); @@ -235,9 +229,6 @@ public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame f TryInitializeTextBuffer(); } - private void NonRoslynTextBuffer_Changed(object sender, TextContentChangedEventArgs e) - => _documentTracker.NonRoslynBufferTextChanged?.Invoke(_documentTracker, EventArgs.Empty); - /// /// Returns the current DocumentId for this window frame. Care must be made with this value, since "current" could change asynchronously as the document /// could be unregistered from a workspace. @@ -293,7 +284,7 @@ private void TryInitializeTextBuffer() { RoslynDebug.Assert(TextBuffer is null); - _documentTracker.AssertIsForeground(); + _documentTracker._threadingContext.ThrowIfNotOnUIThread(); if (ErrorHandler.Succeeded(Frame.GetProperty((int)__VSFPROPID12.VSFPROPID_IsDocDataInitialized, out var boxedIsDocDataInitialized))) { @@ -310,17 +301,10 @@ private void TryInitializeTextBuffer() } } - if (ErrorHandler.Succeeded(Frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out var docData))) + if (ErrorHandler.Succeeded(Frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out var docData)) && + docData is IVsTextBuffer bufferAdapter) { - if (docData is IVsTextBuffer bufferAdapter) - { - TextBuffer = _documentTracker._editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter); - - if (TextBuffer != null && !TextBuffer.ContentType.IsOfType(ContentTypeNames.RoslynContentType)) - { - TextBuffer.Changed += NonRoslynTextBuffer_Changed; - } - } + TextBuffer = _documentTracker._editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter); } return; @@ -328,14 +312,9 @@ private void TryInitializeTextBuffer() private int Disconnect() { - _documentTracker.AssertIsForeground(); + _documentTracker._threadingContext.ThrowIfNotOnUIThread(); _documentTracker.RemoveFrame(this); - if (TextBuffer != null) - { - TextBuffer.Changed -= NonRoslynTextBuffer_Changed; - } - if (_frameEventsCookie != VSConstants.VSCOOKIE_NIL) { return ((IVsWindowFrame2)Frame).Unadvise(_frameEventsCookie); diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs index a403eb5bb9b69..293a3544daf28 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs @@ -41,7 +41,7 @@ internal sealed class VisualStudioDocumentNavigationService( IVsEditorAdaptersFactoryService editorAdaptersFactoryService, // lazy to avoid circularities Lazy sourceGeneratedFileManager) - : ForegroundThreadAffinitizedObject(threadingContext), IDocumentNavigationService + : IDocumentNavigationService { private readonly IServiceProvider _serviceProvider = serviceProvider; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService = editorAdaptersFactoryService; @@ -313,7 +313,7 @@ async static Task OpenDocumentAsync( ISpanMappingService spanMappingService, Document generatedDocument, TextSpan textSpan, CancellationToken cancellationToken) { var results = await spanMappingService.MapSpansAsync( - generatedDocument, SpecializedCollections.SingletonEnumerable(textSpan), cancellationToken).ConfigureAwait(false); + generatedDocument, [textSpan], cancellationToken).ConfigureAwait(false); if (!results.IsDefaultOrEmpty) { diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs index d2ef8a9aca10b..2273efa36396d 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs @@ -12,35 +12,21 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; [ExportWorkspaceServiceFactory(typeof(IDocumentTrackingService), ServiceLayer.Host), Shared] -internal sealed class VisualStudioDocumentTrackingServiceFactory : IWorkspaceServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VisualStudioDocumentTrackingServiceFactory(VisualStudioActiveDocumentTracker activeDocumentTracker) : IWorkspaceServiceFactory { - private readonly VisualStudioActiveDocumentTracker _activeDocumentTracker; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioDocumentTrackingServiceFactory(VisualStudioActiveDocumentTracker activeDocumentTracker) - => _activeDocumentTracker = activeDocumentTracker; - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new VisualStudioDocumentTrackingService(_activeDocumentTracker, workspaceServices.Workspace); + => new VisualStudioDocumentTrackingService(activeDocumentTracker, workspaceServices.Workspace); - private class VisualStudioDocumentTrackingService : IDocumentTrackingService + private class VisualStudioDocumentTrackingService(VisualStudioActiveDocumentTracker activeDocumentTracker, Workspace workspace) : IDocumentTrackingService { - private readonly VisualStudioActiveDocumentTracker _activeDocumentTracker; - private readonly Workspace _workspace; - - public VisualStudioDocumentTrackingService(VisualStudioActiveDocumentTracker activeDocumentTracker, Workspace workspace) - { - _activeDocumentTracker = activeDocumentTracker; - _workspace = workspace; - } - + private readonly VisualStudioActiveDocumentTracker _activeDocumentTracker = activeDocumentTracker; + private readonly Workspace _workspace = workspace; private readonly object _gate = new(); private int _subscriptions = 0; private EventHandler? _activeDocumentChangedEventHandler; - public bool SupportsDocumentTracking => true; - public event EventHandler ActiveDocumentChanged { add @@ -77,19 +63,6 @@ public event EventHandler ActiveDocumentChanged private void ActiveDocumentTracker_DocumentsChanged(object? sender, EventArgs e) => _activeDocumentChangedEventHandler?.Invoke(this, TryGetActiveDocument()); - public event EventHandler NonRoslynBufferTextChanged - { - add - { - _activeDocumentTracker.NonRoslynBufferTextChanged += value; - } - - remove - { - _activeDocumentTracker.NonRoslynBufferTextChanged -= value; - } - } - public DocumentId? TryGetActiveDocument() => _activeDocumentTracker.TryGetActiveDocument(_workspace); diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs index c7f28425d598a..8013cdac4f854 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.FindUsages; @@ -30,31 +31,22 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; [ExportWorkspaceService(typeof(ISymbolNavigationService), ServiceLayer.Host), Shared] -internal partial class VisualStudioSymbolNavigationService : ForegroundThreadAffinitizedObject, ISymbolNavigationService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed partial class VisualStudioSymbolNavigationService( + SVsServiceProvider serviceProvider, + IGlobalOptionService globalOptions, + IThreadingContext threadingContext, + IVsEditorAdaptersFactoryService editorAdaptersFactory, + IMetadataAsSourceFileService metadataAsSourceFileService, + VisualStudioWorkspace workspace) : ISymbolNavigationService { - private readonly IServiceProvider _serviceProvider; - private readonly IGlobalOptionService _globalOptions; - private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory; - private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; - private readonly VisualStudioWorkspace _workspace; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioSymbolNavigationService( - SVsServiceProvider serviceProvider, - IGlobalOptionService globalOptions, - IThreadingContext threadingContext, - IVsEditorAdaptersFactoryService editorAdaptersFactory, - IMetadataAsSourceFileService metadataAsSourceFileService, - VisualStudioWorkspace workspace) - : base(threadingContext) - { - _serviceProvider = serviceProvider; - _globalOptions = globalOptions; - _editorAdaptersFactory = editorAdaptersFactory; - _metadataAsSourceFileService = metadataAsSourceFileService; - _workspace = workspace; - } + private readonly IServiceProvider _serviceProvider = serviceProvider; + private readonly IGlobalOptionService _globalOptions = globalOptions; + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory = editorAdaptersFactory; + private readonly IMetadataAsSourceFileService _metadataAsSourceFileService = metadataAsSourceFileService; + private readonly VisualStudioWorkspace _workspace = workspace; public async Task GetNavigableLocationAsync( ISymbol symbol, Project project, CancellationToken cancellationToken) @@ -122,7 +114,7 @@ public VisualStudioSymbolNavigationService( var navigationTool = _serviceProvider.GetServiceOnMainThread(); return new NavigableLocation(async (options, cancellationToken) => { - await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); return navigationTool.NavigateToNavInfo(navInfo) == VSConstants.S_OK; }); } @@ -143,7 +135,7 @@ public VisualStudioSymbolNavigationService( return new NavigableLocation(async (options, cancellationToken) => { - await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var vsRunningDocumentTable4 = _serviceProvider.GetServiceOnMainThread(); var fileAlreadyOpen = vsRunningDocumentTable4.IsMonikerValid(result.FilePath); @@ -173,7 +165,7 @@ public VisualStudioSymbolNavigationService( var navigationService = editorWorkspace.Services.GetRequiredService(); await navigationService.TryNavigateToSpanAsync( - this.ThreadingContext, + _threadingContext, editorWorkspace, openedDocument.Id, result.IdentifierLocation.SourceSpan, @@ -187,9 +179,7 @@ await navigationService.TryNavigateToSpanAsync( public async Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project project, CancellationToken cancellationToken) { - await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - AssertIsForeground(); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var definitionItem = symbol.ToNonClassifiedDefinitionItem(project.Solution, includeHiddenLocations: true); definitionItem.Properties.TryGetValue(DefinitionItem.RQNameKey1, out var rqName); @@ -230,7 +220,7 @@ public async Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project p var navigateToTextSpan = new Microsoft.VisualStudio.TextManager.Interop.TextSpan[1]; - await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var queryNavigateStatusCode = navigationNotify.QueryNavigateToSymbol( hierarchy, @@ -277,7 +267,7 @@ public async Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project p var documentToUse = generatedDocuments.FirstOrDefault() ?? documents.First(); - await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); if (!TryGetVsHierarchyAndItemId(documentToUse, out var hierarchy, out var itemID)) return null; @@ -291,7 +281,7 @@ public async Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project p private bool TryGetVsHierarchyAndItemId(Document document, [NotNullWhen(true)] out IVsHierarchy? hierarchy, out uint itemID) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (document.Project.Solution.Workspace is VisualStudioWorkspace visualStudioWorkspace && document.FilePath is object) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioTextUndoHistoryWorkspaceServiceFactory.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioTextUndoHistoryWorkspaceServiceFactory.cs index 2cc7c8ce17b37..3ea390c9fc4ad 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioTextUndoHistoryWorkspaceServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioTextUndoHistoryWorkspaceServiceFactory.cs @@ -65,10 +65,8 @@ public bool TryGetTextUndoHistory(Workspace editorWorkspace, ITextBuffer textBuf break; - case MiscellaneousFilesWorkspace _: - + case MiscellaneousFilesWorkspace: // Nothing to do in this case: textBuffer is correct! - break; default: diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioWorkspaceStatusServiceFactory.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioWorkspaceStatusServiceFactory.cs index 4909619e4e1e3..5d4eed5781bde 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioWorkspaceStatusServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioWorkspaceStatusServiceFactory.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.OperationProgress; @@ -23,55 +22,29 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; [ExportWorkspaceServiceFactory(typeof(IWorkspaceStatusService), ServiceLayer.Host), Shared] -internal sealed class VisualStudioWorkspaceStatusServiceFactory : IWorkspaceServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VisualStudioWorkspaceStatusServiceFactory( + SVsServiceProvider serviceProvider, + IThreadingContext threadingContext, + IAsynchronousOperationListenerProvider listenerProvider) : IWorkspaceServiceFactory { - private static readonly Option2 s_partialLoadModeFeatureFlag = new("visual_studio_workspace_partial_load_mode", defaultValue: false); - - private readonly IAsyncServiceProvider2 _serviceProvider; - private readonly IThreadingContext _threadingContext; - private readonly IGlobalOptionService _globalOptions; - private readonly IAsynchronousOperationListener _listener; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioWorkspaceStatusServiceFactory( - SVsServiceProvider serviceProvider, - IThreadingContext threadingContext, - IGlobalOptionService globalOptions, - IAsynchronousOperationListenerProvider listenerProvider) - { - _serviceProvider = (IAsyncServiceProvider2)serviceProvider; - _threadingContext = threadingContext; - _globalOptions = globalOptions; - - // for now, we use workspace so existing tests can automatically wait for full solution load event - // subscription done in test - _listener = listenerProvider.GetListener(FeatureAttribute.Workspace); - } + private readonly IAsyncServiceProvider2 _serviceProvider = (IAsyncServiceProvider2)serviceProvider; + private readonly IAsynchronousOperationListener _listener = listenerProvider.GetListener(FeatureAttribute.Workspace); [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { - if (workspaceServices.Workspace is VisualStudioWorkspace) - { - if (!_globalOptions.GetOption(s_partialLoadModeFeatureFlag)) - { - // don't enable partial load mode for ones that are not in experiment yet - return new WorkspaceStatusService(); - } - - // only VSWorkspace supports partial load mode - return new Service(_serviceProvider, _threadingContext, _listener); - } - - return new WorkspaceStatusService(); + return workspaceServices.Workspace is VisualStudioWorkspace + ? new Service(_serviceProvider, threadingContext, _listener) + : new DefaultWorkspaceStatusService(); } /// /// for prototype, we won't care about what solution is actually fully loaded. /// we will just see whatever solution VS has at this point of time has actually fully loaded /// - private class Service : IWorkspaceStatusService + private sealed class Service : IWorkspaceStatusService { private readonly IAsyncServiceProvider2 _serviceProvider; private readonly IThreadingContext _threadingContext; diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index d1e7bfbf1c0bb..870eb7f11ed19 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ Zpět + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line Začátek řádku @@ -502,6 +512,11 @@ Generovat soubor .editorconfig z nastavení + + Generator running... + Generator running... + + Go To Definition Přejít k definici @@ -1097,6 +1112,11 @@ Preferovat zjednodušenou interpolaci + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Preferovat statické místní funkce @@ -1202,6 +1222,11 @@ V projektu se musí nacházet System.HashCode. + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Obnovit výchozí mapování klávesnice sady Visual Studio @@ -1212,6 +1237,11 @@ Zkontrolovat změny + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} Spustit analýzu kódu {0} @@ -1227,11 +1257,6 @@ Spustit analýzu kódu v samostatném procesu (vyžaduje restartování) - - Run code analysis on latest .NET (requires restart) - Spustit analýzu kódu na nejnovějším rozhraní .NET (vyžaduje restartování) - - Running code analysis for '{0}'... Spouští se analýza kódu pro {0}... @@ -1252,6 +1277,16 @@ Nastavení hledání + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking Výběrem vhodného symbolu zahájíte sledování hodnot. @@ -1292,6 +1327,16 @@ Vybrat členy: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) Zobrazit příkaz Odebrat nepoužívané odkazy v Průzkumníkovi řešení (experimentální) @@ -1317,6 +1362,11 @@ Zobrazit seznam pro doplňování + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else Zobrazit nápovědy pro všechno ostatní @@ -1362,6 +1412,16 @@ 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í. + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace Trasování zásobníku @@ -1377,6 +1437,11 @@ Trasování zásobníku: {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion Návrh @@ -1437,14 +1502,19 @@ Generátor {0}, který vygeneroval tento soubor, přestal tento soubor generovat. Soubor už není součástí projektu. + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? Tato akce se nedá vrátit. Chcete pokračovat? - - This file is auto-generated by the generator '{0}' and cannot be edited. - Tento soubor se automaticky vygeneroval pomocí generátoru {0} a nedá se upravit. + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index 545fe7c9d2db4..4ef28e38ac3f9 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ Zurück + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line Zeilenanfang @@ -502,6 +512,11 @@ EDITORCONFIG-Datei aus Einstellungen generieren + + Generator running... + Generator running... + + Go To Definition Zu Definition wechseln @@ -1097,6 +1112,11 @@ Vereinfachte Interpolation bevorzugen + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Statische lokale Funktionen bevorzugen @@ -1202,6 +1222,11 @@ "System.HashCode" muss im Projekt vorhanden sein. + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Visual Studio-Standardtastenzuordnung zurücksetzen @@ -1212,6 +1237,11 @@ Änderungen überprüfen + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} Code Analysis ausführen für "{0}" @@ -1227,11 +1257,6 @@ Codeanalyse in separatem Prozess ausführen (Neustart erforderlich) - - Run code analysis on latest .NET (requires restart) - Codeanalyse für die neueste .NET-Version ausführen (Neustart erforderlich) - - Running code analysis for '{0}'... Die Codeanalyse für "{0}" wird ausgeführt... @@ -1252,6 +1277,16 @@ Sucheinstellungen + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking Wählen Sie ein geeignetes Symbol aus, um die Wertnachverfolgung zu starten @@ -1292,6 +1327,16 @@ Member auswählen: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) Befehl "Nicht verwendete Verweise entfernen" in Projektmappen-Explorer anzeigen (experimentell) @@ -1317,6 +1362,11 @@ Vervollständigungsliste anzeigen + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else Hinweise für alles andere anzeigen @@ -1362,6 +1412,16 @@ 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. + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace Stapelüberwachung @@ -1377,6 +1437,11 @@ Stapelüberwachung {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion Vorschlag @@ -1437,14 +1502,19 @@ Der Generator "{0}" hat diese Datei nicht vollständig generiert. Diese Datei wird nicht mehr in Ihr Projekt einbezogen. + + The project no longer exists + The project no longer exists + + 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? - - This file is auto-generated by the generator '{0}' and cannot be edited. - Diese Datei wird automatisch vom Generator "{0}" generiert und kann nicht bearbeitet werden. + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. @@ -2271,7 +2341,7 @@ Verwenden Sie die Dropdownliste, um weitere zu dieser Datei gehörige Projekte a camel Case Name - Name mit gemischter Groß-/Kleinschreibung + Name in CamelCase diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index a096c54a69956..01c6dad649d78 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ Atrás + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line Principio de línea @@ -502,6 +512,11 @@ Generar archivo .editorconfig a partir de la configuración + + Generator running... + Generator running... + + Go To Definition Ir a definición @@ -1097,6 +1112,11 @@ Preferir interpolación simplificada + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Preferir funciones locales estáticas @@ -1202,6 +1222,11 @@ Requiere que "System.HashCode" esté presente en el proyecto + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Restablecer asignaciones de teclado predeterminadas de Visual Studio @@ -1212,6 +1237,11 @@ Revisar cambios + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} Ejecutar análisis de código en {0} @@ -1227,11 +1257,6 @@ Ejecutar el análisis de código en un proceso independiente (requiere reiniciar) - - Run code analysis on latest .NET (requires restart) - Ejecutar análisis de código en la versión más reciente de .NET (requiere reiniciar) - - Running code analysis for '{0}'... Ejecutando el análisis de código para "{0}"... @@ -1252,6 +1277,16 @@ Buscar configuración + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking Seleccione un símbolo adecuado para iniciar el seguimiento de valores @@ -1292,6 +1327,16 @@ Seleccionar miembros: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) Mostrar el comando "Quitar referencias sin usar" en el Explorador de soluciones (experimental) @@ -1317,6 +1362,11 @@ Mostrar lista de finalización + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else Mostrar sugerencias para todo lo demás @@ -1362,6 +1412,16 @@ 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. + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace Seguimiento de la pila @@ -1377,6 +1437,11 @@ Seguimiento de la pila {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion Sugerencia @@ -1437,14 +1502,19 @@ El generador "{0}" que creaba este archivo ha dejado de generarlo; el archivo ya no se incluye en el proyecto. + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? Esta acción no se puede deshacer. ¿Quiere continuar? - - This file is auto-generated by the generator '{0}' and cannot be edited. - El generador "{0}" crea este archivo de forma automática y no se puede editar. + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index 6265ab847ad9d..6ccd478502a98 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ Précédent + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line Début de ligne @@ -502,6 +512,11 @@ Générer le fichier .editorconfig à partir des paramètres + + Generator running... + Generator running... + + Go To Definition Accéder à la définition @@ -1097,6 +1112,11 @@ Préférer une interpolation simplifiée + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Préférer les fonctions locales statiques @@ -1202,6 +1222,11 @@ Nécessite la présence de 'System.HashCode' dans le projet + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Réinitialiser la configuration du clavier par défaut de Visual Studio @@ -1212,6 +1237,11 @@ Passer en revue les changements + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} Exécuter une analyse du code sur {0} @@ -1227,11 +1257,6 @@ Exécuter l’analyse du code dans un processus séparé (requiert un redémarrage) - - Run code analysis on latest .NET (requires restart) - Exécutez l'analyse de code sur le dernier .NET (nécessite un redémarrage) - - Running code analysis for '{0}'... Exécution de l'analyse du code pour '{0}'... @@ -1252,6 +1277,16 @@ Paramètres de recherche + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking Sélectionnez un symbole approprié pour démarrer le suivi des valeurs @@ -1292,6 +1327,16 @@ Sélectionner les membres : + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + 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) @@ -1317,6 +1362,11 @@ Afficher la liste de saisie semi-automatique + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else Afficher les indicateurs pour tout le reste @@ -1362,6 +1412,16 @@ 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. + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace Trace de la pile @@ -1377,6 +1437,11 @@ Trace de la pile {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion Suggestion @@ -1437,14 +1502,19 @@ Le générateur '{0}' qui a généré ce fichier a arrêté de générer ce dernier. Le fichier n'est plus inclus dans votre projet. + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? Il est impossible d'annuler cette action. Voulez-vous continuer ? - - This file is auto-generated by the generator '{0}' and cannot be edited. - Ce fichier est généré automatiquement par le générateur '{0}' et ne peut pas être modifié. + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index bac04d1a313bf..e20fa8c9d0a67 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ Indietro + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line Inizio riga @@ -502,6 +512,11 @@ Genera file con estensione editorconfig dalle impostazioni + + Generator running... + Generator running... + + Go To Definition Vai alla definizione @@ -1097,6 +1112,11 @@ Preferire l'interpolazione semplificata + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Preferisci funzioni locali statiche @@ -1202,6 +1222,11 @@ Richiede la presenza di 'System.HashCode' nel progetto + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Reimposta il mapping dei tasti predefinito di Visual Studio @@ -1212,6 +1237,11 @@ Esamina modifiche + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} Esegui Code Analysis su {0} @@ -1227,11 +1257,6 @@ Esegui analisi codice in un processo separato (richiede il riavvio) - - Run code analysis on latest .NET (requires restart) - Esegui Code Analysis nella versione più recente di .NET (richiede il riavvio) - - Running code analysis for '{0}'... Esecuzione di Code Analysis per '{0}'... @@ -1252,6 +1277,16 @@ Impostazioni di ricerca + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking Selezionare un simbolo appropriato per avviare il rilevamento dei valori @@ -1292,6 +1327,16 @@ Selezionare i membri: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) Mostra il comando "Rimuovi riferimenti inutilizzati" in Esplora soluzioni (sperimentale) @@ -1317,6 +1362,11 @@ Mostra l'elenco di completamento + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else Mostra suggerimenti per tutto il resto @@ -1362,6 +1412,16 @@ 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. + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace Analisi dello stack @@ -1377,6 +1437,11 @@ Analisi dello stack {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion Suggerimento @@ -1437,14 +1502,19 @@ Il generatore '{0}' che ha generato questo file non genera più il file. Questo file non è più incluso nel progetto. + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? Questa azione non può essere annullata. Continuare? - - This file is auto-generated by the generator '{0}' and cannot be edited. - Questo file è stato generato automaticamente dal generatore '{0}' e non è modificabile. + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index 6627fa184ad96..a2906f99ae57e 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ 戻る + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line 行の先頭 @@ -502,6 +512,11 @@ 設定から .editorconfig ファイルを生成 + + Generator running... + Generator running... + + Go To Definition 定義へ移動 @@ -1097,6 +1112,11 @@ シンプルな補間を優先する + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions 静的ローカル関数を優先する @@ -1202,6 +1222,11 @@ 'System.HashCode' がプロジェクトに存在する必要があります + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Visual Studio の既定のキーマップをリセットします @@ -1212,6 +1237,11 @@ 変更のプレビュー + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} {0} で Code Analysis を実行 @@ -1227,11 +1257,6 @@ 別のプロセスでコード分析を実行する (再起動が必要) - - Run code analysis on latest .NET (requires restart) - 最新の .NET でコード分析を実行する (再起動が必要) - - Running code analysis for '{0}'... '{0}' のコード分析を実行しています... @@ -1252,6 +1277,16 @@ 検索設定 + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking 値の追跡を開始するための適切なシンボルを選択します @@ -1292,6 +1327,16 @@ メンバーの選択: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) ソリューション エクスプローラーで [未使用の参照を削除する] コマンドを表示する (試験段階) @@ -1317,6 +1362,11 @@ 入力候補一覧の表示 + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else その他すべてのヒントを表示する @@ -1362,6 +1412,16 @@ 一部の配色パターンの色は、[環境] > [フォントおよび色] オプション ページで行われた変更によって上書きされます。[フォントおよび色] オプション ページで [既定値を使用] を選択すると、すべてのカスタマイズが元に戻ります。 + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace スタック トレース @@ -1377,6 +1437,11 @@ スタック トレース {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion 提案事項 @@ -1437,14 +1502,19 @@ このファイルの生成元であるジェネレーター '{0}' で、このファイルの生成が停止しました。このファイルはもうプロジェクトに含まれていません。 + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? この操作を元に戻すことはできません。続行しますか? - - This file is auto-generated by the generator '{0}' and cannot be edited. - このファイルはジェネレーター '{0}' によって自動生成されているため、編集できません。 + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index 19f7d6be1920a..98c46661fa844 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0}({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ 뒤로 + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line 줄의 시작 @@ -502,6 +512,11 @@ 설정에서 .editorconfig 파일 생성 + + Generator running... + Generator running... + + Go To Definition 정의로 이동 @@ -1097,6 +1112,11 @@ 단순화된 보간 선호 + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions 정적 로컬 함수 선호 @@ -1202,6 +1222,11 @@ 'System.HashCode'가 프로젝트에 있어야 합니다. + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Visual Studio 기본 키 매핑을 다시 설정 @@ -1212,6 +1237,11 @@ 변경 내용 검토 + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} {0}에서 코드 분석 실행 @@ -1227,11 +1257,6 @@ 별도의 프로세스에서 코드 분석 실행(다시 시작해야 함) - - Run code analysis on latest .NET (requires restart) - 최신 .NET에서 코드 분석 실행(다시 시작 필요) - - Running code analysis for '{0}'... '{0}'에 대한 코드 분석을 실행하는 중... @@ -1252,6 +1277,16 @@ 검색 설정 + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking 적절한 기호를 선택하여 값 추적 시작 @@ -1292,6 +1327,16 @@ 멤버 선택: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) 솔루션 탐색기에서 "사용하지 않는 참조 제거" 명령 표시(실험적) @@ -1317,6 +1362,11 @@ 완성 목록 표시 + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else 다른 모든 항목에 대한 힌트 표시 @@ -1362,6 +1412,16 @@ 색 구성표의 일부 색이 [환경] > [글꼴 및 색] 옵션 페이지에서 변경한 내용에 따라 재정의됩니다. 모든 사용자 지정을 되돌리려면 [글꼴 및 색] 페이지에서 '기본값 사용'을 선택하세요. + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace 스택 추적 @@ -1377,6 +1437,11 @@ 스택 추적 {0}개 Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion 제안 @@ -1437,14 +1502,19 @@ 이 파일을 생성한 생성기 '{0}'이(가) 이 파일 생성을 중지했습니다. 이 파일은 프로젝트에 더 이상 포함되지 않습니다. + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? 이 작업은 실행 취소할 수 없습니다. 계속하시겠습니까? - - This file is auto-generated by the generator '{0}' and cannot be edited. - 이 파일은 생성기 '{0}'(으)로 자동 생성되며 편집할 수 없습니다. + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index 7f6c51ab3a166..eff47657ecf77 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ Wstecz + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line Początek wiersza @@ -502,6 +512,11 @@ Wygeneruj plik .editorconfig na podstawie ustawień + + Generator running... + Generator running... + + Go To Definition Przejdź do definicji @@ -1097,6 +1112,11 @@ Preferuj uproszczoną interpolację + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Preferuj statyczne funkcje lokalne @@ -1202,6 +1222,11 @@ Wymaga, aby element „System.HashCode” był obecny w projekcie + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Zresetuj domyślne mapowanie klawiszy programu Visual Studio @@ -1212,6 +1237,11 @@ Przejrzyj zmiany + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} Uruchom analizę kodu dla: {0} @@ -1227,11 +1257,6 @@ Uruchom analizę kodu w oddzielnym procesie (wymaga ponownego uruchomienia) - - Run code analysis on latest .NET (requires restart) - Uruchamianie analizy kodu na najnowszej platformie .NET (wymaga ponownego uruchomienia) - - Running code analysis for '{0}'... Trwa analizowanie kodu dla elementu „{0}”... @@ -1252,6 +1277,16 @@ Ustawienia wyszukiwania + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking Wybierz odpowiedni symbol, aby rozpocząć śledzenie wartości @@ -1292,6 +1327,16 @@ Wybierz składowe: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) Pokaż polecenie „Usuń nieużywane odwołania” w Eksploratorze rozwiązań (eksperymentalne) @@ -1317,6 +1362,11 @@ Pokaż listę uzupełniania + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else Pokaż wskazówki dla wszystkich innych elementów @@ -1362,6 +1412,16 @@ 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. + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace Śledzenie stosu @@ -1377,6 +1437,11 @@ Ślad stosu {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion Sugestia @@ -1437,14 +1502,19 @@ Generator „{0}”, który wygenerował ten plik, przestał generować ten plik; ten plik nie jest już uwzględniany w Twoim projekcie. + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? Tej akcji nie można cofnąć. Czy chcesz kontynuować? - - This file is auto-generated by the generator '{0}' and cannot be edited. - Ten plik jest generowany automatycznie przez generator „{0}” i nie może być edytowany. + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index 4ae3db17961c4..dfa6a6fd905c5 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ Voltar + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line Início da linha @@ -502,6 +512,11 @@ Gerar o arquivo .editorconfig das configurações + + Generator running... + Generator running... + + Go To Definition Ir para Definição @@ -1097,6 +1112,11 @@ Prefira interpolação simplificada + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Preferir as funções locais estáticas @@ -1202,6 +1222,11 @@ Requer que 'System.HashCode' esteja presente no projeto + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Redefinir mapeamento de teclas padrão do Visual Studio @@ -1212,6 +1237,11 @@ Revisar alterações + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} Executar Análise de Código em {0} @@ -1227,11 +1257,6 @@ Execute a análise de código em um processo separado (requer reinicialização) - - Run code analysis on latest .NET (requires restart) - Executar análise de código no .NET mais recente (requer reinicialização) - - Running code analysis for '{0}'... Executando a análise de código para '{0}'... @@ -1252,6 +1277,16 @@ Pesquisar as Configurações + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking Selecionar um símbolo apropriado para iniciar o rastreamento de valor @@ -1292,6 +1327,16 @@ Selecionar membros: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + 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) @@ -1317,6 +1362,11 @@ Mostrar a lista de conclusão + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else Mostrar as dicas para tudo @@ -1362,6 +1412,16 @@ 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. + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace Rastreamento de pilha @@ -1377,6 +1437,11 @@ Rastreamento de Pilha{0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion Sugestão @@ -1437,14 +1502,19 @@ O gerador '{0}' que gerou esse arquivo parou de gerá-lo. Esse arquivo não será mais incluído no projeto. + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? Esta ação não pode ser desfeita. Deseja continuar? - - This file is auto-generated by the generator '{0}' and cannot be edited. - Esse arquivo é gerado automaticamente pelo gerador '{0}' e não pode ser editado. + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index 7f92908b97a19..cd9eb81db03a2 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ Назад + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line Начало строки @@ -502,6 +512,11 @@ Создать файл EDITORCONFIG на основе параметров + + Generator running... + Generator running... + + Go To Definition Перейти к определению @@ -1097,6 +1112,11 @@ Предпочитать упрощенную интерполяцию + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Предпочитать статические локальные функции @@ -1202,6 +1222,11 @@ Требуется наличие "System.HashCode" в проекте. + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Сброс схемы назначения клавиш Visual Studio по умолчанию @@ -1212,6 +1237,11 @@ Проверить изменения + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} Запустить Code Analysis для {0} @@ -1227,11 +1257,6 @@ Запуск анализа кода в отдельном процессе (требуется перезапуск) - - Run code analysis on latest .NET (requires restart) - Выполнить анализ кода на последней версии .NET (требуется перезапуск) - - Running code analysis for '{0}'... Выполняется анализ кода для "{0}"… @@ -1252,6 +1277,16 @@ Параметры поиска + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking Выберите соответствующий символ, чтобы начать отслеживание значений @@ -1292,6 +1327,16 @@ Выбрать элементы: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) Показать команду "Удалить неиспользуемые ссылки" в Обозревателе решений (экспериментальная версия) @@ -1317,6 +1362,11 @@ Показать список завершения + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else Отображать подсказки для всех остальных элементов @@ -1362,6 +1412,16 @@ Некоторые цвета цветовой схемы переопределяются изменениями, сделанными на странице "Среда" > "Шрифты и цвета". Выберите "Использовать значения по умолчанию" на странице "Шрифты и цвета", чтобы отменить все настройки. + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace Трассировка стека @@ -1377,6 +1437,11 @@ Трассировка стека {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion Рекомендация @@ -1437,14 +1502,19 @@ Генератор "{0}", создавший этот файл, остановил создание этого файла; этот файл больше не включен в проект. + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? Это действие не может быть отменено. Вы хотите продолжить? - - This file is auto-generated by the generator '{0}' and cannot be edited. - Этот файл создан автоматически генератором "{0}" и не может быть изменен. + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index 88b236ef47313..e56267e6af81e 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ Geri + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line Satır başlangıcı @@ -502,6 +512,11 @@ Ayarlardan .editorconfig dosyası oluştur + + Generator running... + Generator running... + + Go To Definition Tanıma Git @@ -1097,6 +1112,11 @@ Temel ilişkilendirmeyi tercih et + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Statik yerel işlevleri tercih et @@ -1202,6 +1222,11 @@ Projede 'System.HashCode' bulunmasını gerektirir + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping Visual Studio varsayılan tuş eşlemesine sıfırla @@ -1212,6 +1237,11 @@ Değişiklikleri Gözden Geçir + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} {0} Öğesinde Code Analysis Çalıştır @@ -1227,11 +1257,6 @@ Kod analizini ayrı bir işlemde çalıştır (yeniden başlatma gerektirir) - - Run code analysis on latest .NET (requires restart) - En son .NET'te kod analizi çalıştırın (yeniden başlatma gerektirir) - - Running code analysis for '{0}'... '{0}' için kod analizi çalıştırılıyor... @@ -1252,6 +1277,16 @@ Arama Ayarları + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking Değer takibini başlatmak için uygun bir sembol seçin @@ -1292,6 +1327,16 @@ Üye seçin: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) Çözüm Gezgini'nde "Kullanılmayan Başvuruları Kaldır" komutunu göster (deneysel) @@ -1317,6 +1362,11 @@ Tamamlama listesini göster + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else Diğer her şey için ipuçlarını göster @@ -1362,6 +1412,16 @@ 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. + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace Yığın İzleme @@ -1377,6 +1437,11 @@ Yığın İzleme {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion Öneri @@ -1437,14 +1502,19 @@ Bu dosyayı oluşturan '{0}' oluşturucusu bu dosyayı oluşturmayı durdurdu; bu dosya artık projenize dahil edilmiyor. + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? Bu işlem geri alınamaz. Devam etmek istiyor musunuz? - - This file is auto-generated by the generator '{0}' and cannot be edited. - Bu dosya, '{0}' oluşturucusu tarafından otomatik olarak oluşturuldu ve düzenlenemez. + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index b84781af1c18d..4125ad72481bd 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ 后退 + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line 行首 @@ -502,6 +512,11 @@ 基于设置生成 .editorconfig 文件 + + Generator running... + Generator running... + + Go To Definition 转到定义 @@ -1097,6 +1112,11 @@ 首选简化内插 + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions 首选静态本地函数 @@ -1202,6 +1222,11 @@ 要求项目中存在 "System.HashCode" + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping 重置 Visual Studio 默认项映射 @@ -1212,6 +1237,11 @@ 预览更改 + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} 对 {0} 运行 Code Analysis @@ -1227,11 +1257,6 @@ 在单独的进程中运行代码分析(需要重启) - - Run code analysis on latest .NET (requires restart) - 在最新 .NET 上运行代码分析(需要重启) - - Running code analysis for '{0}'... 正在为“{0}”运行代码分析… @@ -1252,6 +1277,16 @@ 搜索设置 + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking 选择适当的符号以启动值跟踪 @@ -1292,6 +1327,16 @@ 选择成员: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) 在解决方案资源管理器中显示“删除未使用的引用”命令(实验性) @@ -1317,6 +1362,11 @@ 显示完成列表 + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else 显示其他所有内容的提示 @@ -1362,6 +1412,16 @@ 在“环境”>“字体和颜色”选项页中所做的更改将替代某些配色方案颜色。在“字体和颜色”页中选择“使用默认值”,还原所有自定义项。 + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace 堆栈跟踪 @@ -1377,6 +1437,11 @@ 堆栈跟踪: {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion 建议 @@ -1437,14 +1502,19 @@ 生成此文件的生成器“{0}”已停止生成此文件;项目中不再包含此文件。 + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? 此操作无法撤消。是否要继续? - - This file is auto-generated by the generator '{0}' and cannot be edited. - 此文件由生成器“{0}”自动生成,无法编辑。 + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index 6acce6f8d8af4..2abaa2a348650 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -119,7 +119,12 @@ {0} ({1}) - {0} ({1}) + {0} ({1}) + + + + Automatic. Run generators after any change + Automatic. Run generators after any change @@ -147,6 +152,11 @@ 返回 + + Balanced. Run generators after saving or building + Balanced. Run generators after saving or building + + Beginning of line 行首 @@ -502,6 +512,11 @@ 從設定產生 .editorconfig 檔案 + + Generator running... + Generator running... + + Go To Definition 移至定義 @@ -1097,6 +1112,11 @@ 優先使用簡易動畫插補 + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions 優先使用靜態區域函式 @@ -1202,6 +1222,11 @@ 專案中必須有 'System.HashCode' + + Rerun generator + Rerun generator + + Reset Visual Studio default keymapping 重設 Visual Studio 預設按鍵對應 @@ -1212,6 +1237,11 @@ 檢閱變更 + + Roslyn save command handler + Roslyn save command handler + + Run Code Analysis on {0} 對 {0} 執行 Code Analysis @@ -1227,11 +1257,6 @@ 在獨立的流程中執行程式碼分析 (需要重新開機) - - Run code analysis on latest .NET (requires restart) - 針對最新的 .NET 執行程式碼分析 (需要重新啟動) - - Running code analysis for '{0}'... 正在執行 '{0}' 的程式碼分析... @@ -1252,6 +1277,16 @@ 搜尋設定 + + Search canceled + Search canceled + + + + Search found no results: no C# or Visual Basic projects are opened + Search found no results: no C# or Visual Basic projects are opened + + Select an appropriate symbol to start value tracking 選取適當的符號以開始值追蹤 @@ -1292,6 +1327,16 @@ 選取成員: + + Semantic Search ({0}) + Semantic Search ({0}) + + + + Semantic Search Results + Semantic Search Results + + Show "Remove Unused References" command in Solution Explorer (experimental) 在方案總管 (實驗性) 中顯示「移除未使用的參考」命令 @@ -1317,6 +1362,11 @@ 顯示自動完成清單 + + Show guides for comments and preprocessor regions + Show guides for comments and preprocessor regions + + Show hints for everything else 顯示所有其他項目的提示 @@ -1362,6 +1412,16 @@ [環境] > [字型和色彩選項] 頁面中所做的變更覆寫了某些色彩配置的色彩。請選擇 [字型和色彩] 頁面中的 [使用預設] 來還原所有自訂。 + + Source Generators + Source Generators + + + + Source generator execution (requires restart): + Source generator execution (requires restart): + + Stack Trace 堆疊追蹤 @@ -1377,6 +1437,11 @@ 堆疊追蹤 {0} Header for numbered stack trace view tabs + + Strike out obsolete symbols + Strike out obsolete symbols + + Suggestion 建議 @@ -1437,14 +1502,19 @@ 產生此檔案的產生器 '{0}' 已停止產生此檔案; 此檔案已不再包含在您的專案中。 + + The project no longer exists + The project no longer exists + + This action cannot be undone. Do you wish to continue? 此動作無法復原。要繼續嗎? - - This file is auto-generated by the generator '{0}' and cannot be edited. - 此檔案是由產生器 '{0}' 自動產生,無法加以編輯。 + + This file was generated by '{0}' at {1} and cannot be edited. + This file was generated by '{0}' at {1} and cannot be edited. diff --git a/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.AbstractNodeNameGenerator.cs b/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.AbstractNodeNameGenerator.cs index d6e7c57d32ec6..8abf72e54eb40 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.AbstractNodeNameGenerator.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.AbstractNodeNameGenerator.cs @@ -23,7 +23,7 @@ protected abstract class AbstractNodeNameGenerator protected static void AppendDotIfNeeded(StringBuilder builder) { if (builder.Length > 0 && - char.IsLetterOrDigit(builder[builder.Length - 1])) + char.IsLetterOrDigit(builder[^1])) { builder.Append('.'); } diff --git a/src/VisualStudio/Core/Impl/CodeModel/Collections/BasesCollection.cs b/src/VisualStudio/Core/Impl/CodeModel/Collections/BasesCollection.cs index 956967a64650b..1f9c1865cc642 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/Collections/BasesCollection.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/Collections/BasesCollection.cs @@ -80,7 +80,7 @@ private IEnumerable GetBaseTypes() } else { - return SpecializedCollections.SingletonEnumerable(symbol.BaseType); + return [symbol.BaseType]; } } diff --git a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs index 3a1213d0d2013..4a980f4e05115 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs @@ -141,7 +141,7 @@ private static object[] GetValidArray(object itemOrArray, bool allowMultipleElem } } - return result.ToArray(); + return [.. result]; } internal EnvDTE80.CodeAttributeArgument AddAttributeArgument(SyntaxNode containerNode, string name, string value, object position) diff --git a/src/VisualStudio/Core/Impl/CodeModel/MetadataNameHelpers.cs b/src/VisualStudio/Core/Impl/CodeModel/MetadataNameHelpers.cs index dc66adb5f888d..4de2a0783c3fb 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/MetadataNameHelpers.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/MetadataNameHelpers.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel { @@ -67,7 +68,7 @@ public static string GetMetadataName(ITypeSymbol typeSymbol) throw new ArgumentException("Type parameters are not suppported", nameof(typeSymbol)); } - var parts = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var parts); ISymbol symbol = typeSymbol; while (symbol != null) @@ -78,10 +79,8 @@ public static string GetMetadataName(ITypeSymbol typeSymbol) var builder = new StringBuilder(); - while (parts.Count > 0) + while (parts.TryPop(out symbol)) { - symbol = parts.Pop(); - if (builder.Length > 0) { if (symbol.ContainingSymbol is ITypeSymbol) diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index b6f80d5508f2d..3c1acd9e7c9aa 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -5,33 +5,27 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Immutable; using System.ComponentModel.Composition; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.Collections; -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.LanguageService; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; -using Task = System.Threading.Tasks.Task; namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel { [Export(typeof(IProjectCodeModelFactory))] [Export(typeof(ProjectCodeModelFactory))] - internal sealed class ProjectCodeModelFactory : ForegroundThreadAffinitizedObject, IProjectCodeModelFactory + internal sealed class ProjectCodeModelFactory : IProjectCodeModelFactory { private readonly ConcurrentDictionary _projectCodeModels = []; @@ -51,7 +45,6 @@ public ProjectCodeModelFactory( IGlobalOptionService globalOptions, IThreadingContext threadingContext, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, assertIsForeground: false) { _visualStudioWorkspace = visualStudioWorkspace; _serviceProvider = serviceProvider; @@ -147,6 +140,21 @@ void FireEventsForDocument(DocumentId documentId) codeModel.FireEvents(); return; } + + // Returns true if any keyboard or mouse button input is pending on the message queue. + static bool IsInputPending() + { + // The code below invokes into user32.dll, which is not available in non-Windows. + if (PlatformInformation.IsUnix) + return false; + + // The return value of GetQueueStatus is HIWORD:LOWORD. + // A non-zero value in HIWORD indicates some input message in the queue. + var result = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT); + + const uint InputMask = NativeMethods.QS_INPUT | (NativeMethods.QS_INPUT << 16); + return (result & InputMask) != 0; + } } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) diff --git a/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs b/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs index 3ad47a5d3fa47..45aeb866aeb75 100644 --- a/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs +++ b/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs @@ -191,6 +191,40 @@ private protected void BindToOption(ComboBox comboBox, PerLanguageOption2 _bindingExpressions.Add(bindingExpression); } + private protected void BindToOption(RadioButton radiobutton, Option2 optionKey, T optionValue) + { + var binding = new Binding() + { + Source = new OptionBinding(OptionStore, optionKey), + Path = new PropertyPath("Value"), + UpdateSourceTrigger = UpdateSourceTrigger.Default, + Converter = new RadioButtonCheckedConverter(), + ConverterParameter = optionValue + }; + + AddSearchHandler(radiobutton); + + var bindingExpression = radiobutton.SetBinding(RadioButton.IsCheckedProperty, binding); + _bindingExpressions.Add(bindingExpression); + } + + private protected void BindToOption(RadioButton radioButton, Option2 nullableOptionKey, T optionValue, Func onNullValue) where T : struct + { + var binding = new Binding() + { + Source = new OptionBinding(OptionStore, nullableOptionKey), + Path = new PropertyPath("Value"), + UpdateSourceTrigger = UpdateSourceTrigger.Default, + Converter = new RadioButtonCheckedConverter(onNullValue), + ConverterParameter = optionValue, + }; + + AddSearchHandler(radioButton); + + var bindingExpression = radioButton.SetBinding(RadioButton.IsCheckedProperty, binding); + _bindingExpressions.Add(bindingExpression); + } + private protected void BindToOption(RadioButton radiobutton, PerLanguageOption2 optionKey, T optionValue, string languageName) { var binding = new Binding() @@ -255,19 +289,26 @@ private protected void AddSearchHandler(ContentControl control) } } - public class RadioButtonCheckedConverter : IValueConverter + public sealed class RadioButtonCheckedConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, - System.Globalization.CultureInfo culture) - { - return value.Equals(parameter); - } + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + => value.Equals(parameter); - public object ConvertBack(object value, Type targetType, object parameter, - System.Globalization.CultureInfo culture) - { - return value.Equals(true) ? parameter : Binding.DoNothing; - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => value.Equals(true) ? parameter : Binding.DoNothing; + } + + public sealed class RadioButtonCheckedConverter(Func onNullValue) : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + => value switch + { + null => onNullValue(), + _ => value.Equals(parameter), + }; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => value.Equals(true) ? parameter : Binding.DoNothing; } public class ComboBoxItemTagToIndexConverter : IValueConverter diff --git a/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs b/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs index 9ca3dba4d904b..94ff8208e4117 100644 --- a/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs +++ b/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs @@ -148,7 +148,7 @@ public void UpdatePreview(string text) _editorOptions.CreateOptions(), textBuffer.CurrentSnapshot, separator: "", - exposedLineSpans: GetExposedLineSpans(textBuffer.CurrentSnapshot).ToArray()); + exposedLineSpans: [.. GetExposedLineSpans(textBuffer.CurrentSnapshot)]); var textView = _textEditorFactoryService.CreateTextView(projection, _textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Interactive)); @@ -224,7 +224,7 @@ protected void AddParenthesesOption( isChecked: !defaultAddForClarity)); CodeStyleItems.Add(new EnumCodeStyleOptionViewModel( - languageOption, title, preferences.ToArray(), + languageOption, title, [.. preferences], examples, this, optionStore, ServicesVSResources.Parentheses_preferences_colon, codeStylePreferences)); } diff --git a/src/VisualStudio/Core/Impl/Options/Converters/NullableBoolOptionConverter.cs b/src/VisualStudio/Core/Impl/Options/Converters/NullableBoolOptionConverter.cs index ffe8fd7fd083a..1d485871823e2 100644 --- a/src/VisualStudio/Core/Impl/Options/Converters/NullableBoolOptionConverter.cs +++ b/src/VisualStudio/Core/Impl/Options/Converters/NullableBoolOptionConverter.cs @@ -7,25 +7,18 @@ using System.Windows; using System.Windows.Data; -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options.Converters +namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options.Converters; + +internal sealed class NullableBoolOptionConverter(Func onNullValue) : IValueConverter { - internal class NullableBoolOptionConverter : IValueConverter - { - private readonly Func _onNullValue; - public NullableBoolOptionConverter(Func onNullValue) + public object Convert(object? value, Type targetType, object parameter, CultureInfo culture) + => value switch { - _onNullValue = onNullValue; - } - - public object Convert(object? value, Type targetType, object parameter, CultureInfo culture) - => value switch - { - null => _onNullValue(), - bool b => b, - _ => DependencyProperty.UnsetValue - }; + null => onNullValue(), + bool b => b, + _ => DependencyProperty.UnsetValue + }; - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - => value; - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => value; } diff --git a/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs b/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs index 983d1474bfea5..75f2401bcaaf0 100644 --- a/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs +++ b/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs @@ -72,7 +72,7 @@ private void AddButton_Click(object sender, RoutedEventArgs e) private void ManageSpecificationsButton_Click(object sender, RoutedEventArgs e) { - var viewModel = new ManageSymbolSpecificationsDialogViewModel(_viewModel.Specifications, _viewModel.CodeStyleItems.ToList(), _languageName, _notificationService); + var viewModel = new ManageSymbolSpecificationsDialogViewModel(_viewModel.Specifications, [.. _viewModel.CodeStyleItems], _languageName, _notificationService); var dialog = new ManageNamingStylesInfoDialog(viewModel); if (dialog.ShowModal().Value == true) { @@ -82,7 +82,7 @@ private void ManageSpecificationsButton_Click(object sender, RoutedEventArgs e) private void ManageStylesButton_Click(object sender, RoutedEventArgs e) { - var viewModel = new ManageNamingStylesDialogViewModel(_viewModel.NamingStyles, _viewModel.CodeStyleItems.ToList(), _notificationService); + var viewModel = new ManageNamingStylesDialogViewModel(_viewModel.NamingStyles, [.. _viewModel.CodeStyleItems], _notificationService); var dialog = new ManageNamingStylesInfoDialog(viewModel); if (dialog.ShowModal().Value == true) { diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/BaseItem.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/BaseItem.cs index 1700f46c598b1..27d1a9fe951ac 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/BaseItem.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/BaseItem.cs @@ -42,7 +42,7 @@ public BaseItem(string name) _name = name; } - public IEnumerable Children => SpecializedCollections.EmptyEnumerable(); + public IEnumerable Children => []; public bool IsExpandable => true; diff --git a/src/VisualStudio/Core/Test.Next/Options/VisualStudioSettingsOptionPersisterTests.cs b/src/VisualStudio/Core/Test.Next/Options/VisualStudioSettingsOptionPersisterTests.cs index 59fa7df3cca64..7381a03351a4f 100644 --- a/src/VisualStudio/Core/Test.Next/Options/VisualStudioSettingsOptionPersisterTests.cs +++ b/src/VisualStudio/Core/Test.Next/Options/VisualStudioSettingsOptionPersisterTests.cs @@ -100,17 +100,6 @@ private static (object? value, object? storageValue) GetSomeOptionValue(Type opt optionType == typeof(ImmutableArray) ? (ImmutableArray.Create("a", "b"), new[] { "a", "b" }) : throw ExceptionUtilities.UnexpectedValue(optionType); - private static Type GetStorageType(Type optionType) - => optionType.IsEnum ? typeof(int) : - (Nullable.GetUnderlyingType(optionType)?.IsEnum == true) ? typeof(int?) : - optionType == typeof(NamingStylePreferences) ? typeof(string) : - typeof(ICodeStyleOption2).IsAssignableFrom(optionType) ? typeof(string) : - optionType == typeof(ImmutableArray) ? typeof(string[]) : - optionType == typeof(ImmutableArray) ? typeof(bool[]) : - optionType == typeof(ImmutableArray) ? typeof(int[]) : - optionType == typeof(ImmutableArray) ? typeof(long[]) : - optionType; - private static bool IsDefaultImmutableArray(object array) => (bool)array.GetType().GetMethod("get_IsDefault").Invoke(array, [])!; @@ -229,11 +218,10 @@ public void SettingsManagerReadOptionValue_Error( Type optionType) { var (optionValue, storageValue) = GetSomeOptionValue(optionType); - var storageType = GetStorageType(optionType); var mockManager = new MockSettingsManager() { - GetValueImpl = (_, type) => (type == storageType ? specializedTypeResult : GetValueResult.Success, storageValue) + GetValueImpl = (_, type) => (specializedTypeResult, storageValue) }; var result = VisualStudioSettingsOptionPersister.TryReadOptionValue(mockManager, "key", optionType, optionValue); diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index 8b661cec79e46..632755a0b1a82 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; using Microsoft.VisualStudio.PlatformUI; using Roslyn.Test.Utilities; @@ -21,15 +22,19 @@ namespace Microsoft.CodeAnalysis.Remote.UnitTests { internal sealed class SerializationValidator { - private sealed class AssetProvider : AbstractAssetProvider + private sealed class AssetProvider(SerializationValidator validator) : AbstractAssetProvider { - private readonly SerializationValidator _validator; + public override async ValueTask GetAssetAsync(AssetPath assetPath, Checksum checksum, CancellationToken cancellationToken) + => await validator.GetValueAsync(checksum).ConfigureAwait(false); - public AssetProvider(SerializationValidator validator) - => _validator = validator; - - public override async ValueTask GetAssetAsync(AssetHint assetHint, Checksum checksum, CancellationToken cancellationToken) - => await _validator.GetValueAsync(checksum).ConfigureAwait(false); + public override async Task GetAssetsAsync(AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) where TArg : default + { + foreach (var checksum in checksums) + { + var value = await GetAssetAsync(assetPath, checksum, cancellationToken).ConfigureAwait(false); + callback?.Invoke(checksum, value, arg!); + } + } } internal sealed class ChecksumObjectCollection : IEnumerable @@ -50,10 +55,10 @@ internal sealed class ChecksumObjectCollection : IEnumerable /// public readonly Checksum Checksum; - public ChecksumObjectCollection(SerializationValidator validator, ChecksumCollection collection) + public ChecksumObjectCollection(SerializationValidator validator, WellKnownSynchronizationKind kind, ChecksumCollection collection) { Checksum = collection.Checksum; - Kind = collection.GetWellKnownSynchronizationKind(); + Kind = kind; // using .Result here since we don't want to convert all calls to this to async. // and none of ChecksumWithChildren actually use async @@ -89,11 +94,10 @@ public async Task GetValueAsync(Checksum checksum) var data = await GetRequiredAssetAsync(checksum).ConfigureAwait(false); Contract.ThrowIfNull(data.Value); - using var context = new SolutionReplicationContext(); using var stream = SerializableBytes.CreateWritableStream(); using (var writer = new ObjectWriter(stream, leaveOpen: true)) { - Serializer.Serialize(data.Value, writer, context, CancellationToken.None); + Serializer.Serialize(data.Value, writer, CancellationToken.None); } stream.Position = 0; @@ -115,10 +119,7 @@ public async Task GetSolutionAsync(SolutionAssetStorage.Scope scope) } public ChecksumObjectCollection ToProjectObjects(ChecksumCollection collection) - => new(this, collection); - - public ChecksumObjectCollection ToDocumentObjects(ChecksumCollection collection) - => new(this, collection); + => new(this, WellKnownSynchronizationKind.ProjectState, collection); internal async Task VerifyAssetAsync(SolutionStateChecksums solutionObject) { @@ -169,37 +170,25 @@ await VerifyAssetSerializationAsync( (v, k, s) => new SolutionAsset(s.CreateChecksum(v, CancellationToken.None), v)); } - foreach (var (checksum, documentId) in projectObject.Documents) - { - var documentObject = await GetValueAsync(checksum).ConfigureAwait(false); - Assert.Equal(documentObject.DocumentId, documentId); - await VerifyAssetAsync(documentObject).ConfigureAwait(false); - } + foreach (var (attributeChecksum, textChecksum, documentId) in projectObject.Documents) + await VerifyAssetAsync(attributeChecksum, textChecksum).ConfigureAwait(false); - foreach (var (checksum, documentId) in projectObject.AdditionalDocuments) - { - var documentObject = await GetValueAsync(checksum).ConfigureAwait(false); - Assert.Equal(documentObject.DocumentId, documentId); - await VerifyAssetAsync(documentObject).ConfigureAwait(false); - } + foreach (var (attributeChecksum, textChecksum, documentId) in projectObject.AdditionalDocuments) + await VerifyAssetAsync(attributeChecksum, textChecksum).ConfigureAwait(false); - foreach (var (checksum, documentId) in projectObject.AnalyzerConfigDocuments) - { - var documentObject = await GetValueAsync(checksum).ConfigureAwait(false); - Assert.Equal(documentObject.DocumentId, documentId); - await VerifyAssetAsync(documentObject).ConfigureAwait(false); - } + foreach (var (attributeChecksum, textChecksum, documentId) in projectObject.AnalyzerConfigDocuments) + await VerifyAssetAsync(attributeChecksum, textChecksum).ConfigureAwait(false); } - internal async Task VerifyAssetAsync(DocumentStateChecksums documentObject) + internal async Task VerifyAssetAsync(Checksum attributeChecksum, Checksum textChecksum) { var info = await VerifyAssetSerializationAsync( - documentObject.Info, WellKnownSynchronizationKind.DocumentAttributes, + attributeChecksum, WellKnownSynchronizationKind.DocumentAttributes, (v, k, s) => new SolutionAsset(v.Checksum, v)).ConfigureAwait(false); await VerifyAssetSerializationAsync( - documentObject.Text, WellKnownSynchronizationKind.SerializableSourceText, - (v, k, s) => new SolutionAsset(s.CreateChecksum(v, CancellationToken.None), v)); + textChecksum, WellKnownSynchronizationKind.SerializableSourceText, + (v, k, s) => new SolutionAsset(v.ContentChecksum, v)); } internal async Task VerifyAssetSerializationAsync( @@ -232,13 +221,6 @@ internal async Task VerifySolutionStateSerializationAsync(Solution solution, Che SolutionStateEqual(solutionObjectFromSolution, solutionObjectFromSyncObject); } - private static void AssertChecksumCollectionEqual( - ChecksumsAndIds collection1, ChecksumsAndIds collection2) - { - AssertChecksumCollectionEqual(collection1.Checksums, collection2.Checksums); - AssertEx.Equal(collection1.Ids, collection2.Ids); - } - private static void AssertChecksumCollectionEqual( ChecksumCollection collection1, ChecksumCollection collection2) { @@ -246,6 +228,15 @@ private static void AssertChecksumCollectionEqual( AssertEx.Equal(collection1.Children, collection2.Children); } + private static void AssertDocumentChecksumCollectionEqual( + DocumentChecksumsAndIds collection1, DocumentChecksumsAndIds collection2) + { + Assert.Equal(collection1.Checksum, collection2.Checksum); + AssertEx.Equal(collection1.AttributeChecksums, collection2.AttributeChecksums); + AssertEx.Equal(collection1.TextChecksums, collection2.TextChecksums); + AssertEx.Equal(collection1.Ids, collection2.Ids); + } + internal void SolutionCompilationStateEqual(SolutionCompilationStateChecksums solutionObject1, SolutionCompilationStateChecksums solutionObject2) { Assert.Equal(solutionObject1.Checksum, solutionObject2.Checksum); @@ -255,20 +246,21 @@ internal void SolutionCompilationStateEqual(SolutionCompilationStateChecksums so Assert.Equal(solutionObject1.FrozenSourceGeneratedDocuments.HasValue, solutionObject2.FrozenSourceGeneratedDocuments.HasValue); if (solutionObject1.FrozenSourceGeneratedDocuments.HasValue) - AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocuments.Value, solutionObject2.FrozenSourceGeneratedDocuments!.Value); + AssertDocumentChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocuments.Value, solutionObject2.FrozenSourceGeneratedDocuments!.Value); } internal void SolutionStateEqual(SolutionStateChecksums solutionObject1, SolutionStateChecksums solutionObject2) { Assert.Equal(solutionObject1.Checksum, solutionObject2.Checksum); Assert.Equal(solutionObject1.Attributes, solutionObject2.Attributes); - AssertChecksumCollectionEqual(solutionObject1.Projects, solutionObject2.Projects); + AssertEx.Equals(solutionObject1.Projects.Ids, solutionObject2.Projects.Ids); + AssertChecksumCollectionEqual(solutionObject1.Projects.Checksums, solutionObject2.Projects.Checksums); AssertChecksumCollectionEqual(solutionObject1.AnalyzerReferences, solutionObject2.AnalyzerReferences); ProjectStatesEqual(ToProjectObjects(solutionObject1.Projects.Checksums), ToProjectObjects(solutionObject2.Projects.Checksums)); } - private void ProjectStateEqual(ProjectStateChecksums projectObjects1, ProjectStateChecksums projectObjects2) + private static void ProjectStateEqual(ProjectStateChecksums projectObjects1, ProjectStateChecksums projectObjects2) { Assert.Equal(projectObjects1.Checksum, projectObjects2.Checksum); Assert.Equal(projectObjects1.Info, projectObjects2.Info); @@ -277,23 +269,12 @@ private void ProjectStateEqual(ProjectStateChecksums projectObjects1, ProjectSta AssertChecksumCollectionEqual(projectObjects1.ProjectReferences, projectObjects2.ProjectReferences); AssertChecksumCollectionEqual(projectObjects1.MetadataReferences, projectObjects2.MetadataReferences); AssertChecksumCollectionEqual(projectObjects1.AnalyzerReferences, projectObjects2.AnalyzerReferences); - AssertChecksumCollectionEqual(projectObjects1.Documents, projectObjects2.Documents); - AssertChecksumCollectionEqual(projectObjects1.AdditionalDocuments, projectObjects2.AdditionalDocuments); - AssertChecksumCollectionEqual(projectObjects1.AnalyzerConfigDocuments, projectObjects2.AnalyzerConfigDocuments); - - DocumentStatesEqual(ToDocumentObjects(projectObjects1.Documents.Checksums), ToDocumentObjects(projectObjects2.Documents.Checksums)); - DocumentStatesEqual(ToDocumentObjects(projectObjects1.AdditionalDocuments.Checksums), ToDocumentObjects(projectObjects2.AdditionalDocuments.Checksums)); - DocumentStatesEqual(ToDocumentObjects(projectObjects1.AnalyzerConfigDocuments.Checksums), ToDocumentObjects(projectObjects2.AnalyzerConfigDocuments.Checksums)); - } - - private static void DocumentStateEqual(DocumentStateChecksums documentObjects1, DocumentStateChecksums documentObjects2) - { - Assert.Equal(documentObjects1.Checksum, documentObjects2.Checksum); - Assert.Equal(documentObjects1.Info, documentObjects2.Info); - Assert.Equal(documentObjects1.Text, documentObjects2.Text); + AssertDocumentChecksumCollectionEqual(projectObjects1.Documents, projectObjects2.Documents); + AssertDocumentChecksumCollectionEqual(projectObjects1.AdditionalDocuments, projectObjects2.AdditionalDocuments); + AssertDocumentChecksumCollectionEqual(projectObjects1.AnalyzerConfigDocuments, projectObjects2.AnalyzerConfigDocuments); } - private void ProjectStatesEqual(ChecksumObjectCollection projectObjects1, ChecksumObjectCollection projectObjects2) + private static void ProjectStatesEqual(ChecksumObjectCollection projectObjects1, ChecksumObjectCollection projectObjects2) { SynchronizationObjectEqual(projectObjects1, projectObjects2); @@ -303,16 +284,6 @@ private void ProjectStatesEqual(ChecksumObjectCollection ProjectStateEqual(projectObjects1[i], projectObjects2[i]); } - private static void DocumentStatesEqual(ChecksumObjectCollection documentObjects1, ChecksumObjectCollection documentObjects2) - { - SynchronizationObjectEqual(documentObjects1, documentObjects2); - - Assert.Equal(documentObjects1.Count, documentObjects2.Count); - - for (var i = 0; i < documentObjects1.Count; i++) - DocumentStateEqual(documentObjects1[i], documentObjects2[i]); - } - internal async Task VerifySnapshotInServiceAsync( ProjectStateChecksums projectObject, int expectedDocumentCount, @@ -326,13 +297,13 @@ internal async Task VerifySnapshotInServiceAsync( await VerifyChecksumInServiceAsync(projectObject.CompilationOptions, WellKnownSynchronizationKind.CompilationOptions).ConfigureAwait(false); await VerifyChecksumInServiceAsync(projectObject.ParseOptions, WellKnownSynchronizationKind.ParseOptions).ConfigureAwait(false); - await VerifyCollectionInService(ToDocumentObjects(projectObject.Documents.Checksums), expectedDocumentCount).ConfigureAwait(false); + Assert.Equal(expectedDocumentCount, projectObject.Documents.Ids.Length); await VerifyCollectionInService(projectObject.ProjectReferences, expectedProjectReferenceCount, WellKnownSynchronizationKind.ProjectReference).ConfigureAwait(false); await VerifyCollectionInService(projectObject.MetadataReferences, expectedMetadataReferenceCount, WellKnownSynchronizationKind.MetadataReference).ConfigureAwait(false); await VerifyCollectionInService(projectObject.AnalyzerReferences, expectedAnalyzerReferenceCount, WellKnownSynchronizationKind.AnalyzerReference).ConfigureAwait(false); - await VerifyCollectionInService(ToDocumentObjects(projectObject.AdditionalDocuments.Checksums), expectedAdditionalDocumentCount).ConfigureAwait(false); + Assert.Equal(expectedAdditionalDocumentCount, projectObject.AdditionalDocuments.Ids.Length); } internal async Task VerifyCollectionInService(ChecksumCollection checksums, int expectedCount, WellKnownSynchronizationKind expectedItemKind) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs index 44e0d9b7ccaaa..d638391df2977 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs @@ -535,7 +535,8 @@ public async Task EmptyAssetChecksumTest() var document = CreateWorkspace().CurrentSolution.AddProject("empty", "empty", LanguageNames.CSharp).AddDocument("empty", SourceText.From("")); var serializer = document.Project.Solution.Services.GetService(); - var source = serializer.CreateChecksum(await document.GetTextAsync().ConfigureAwait(false), CancellationToken.None); + var text = await document.GetTextAsync().ConfigureAwait(false); + var source = new SerializableSourceText(text, text.GetContentHash()).ContentChecksum; var metadata = serializer.CreateChecksum(new MissingMetadataReference(), CancellationToken.None); var analyzer = serializer.CreateChecksum(new AnalyzerFileReference(Path.Combine(TempRoot.Root, "missing"), new MissingAnalyzerLoader()), CancellationToken.None); @@ -607,40 +608,38 @@ public void TestEncodingSerialization() // test with right serializable encoding var sourceText = SourceText.From("Hello", Encoding.UTF8); + var serializableSourceText = new SerializableSourceText(sourceText, sourceText.GetContentHash()); using (var stream = SerializableBytes.CreateWritableStream()) { - using var context = new SolutionReplicationContext(); - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(sourceText, objectWriter, context, CancellationToken.None); + serializer.Serialize(serializableSourceText, objectWriter, CancellationToken.None); } stream.Position = 0; using var objectReader = ObjectReader.TryGetReader(stream); - var newText = (SourceText)serializer.Deserialize(sourceText.GetWellKnownSynchronizationKind(), objectReader, CancellationToken.None); - Assert.Equal(sourceText.ToString(), newText.ToString()); + var newText = (SerializableSourceText)serializer.Deserialize(serializableSourceText.GetWellKnownSynchronizationKind(), objectReader, CancellationToken.None); + Assert.Equal(sourceText.ToString(), newText.GetText(CancellationToken.None).ToString()); } // test with wrong encoding that doesn't support serialization sourceText = SourceText.From("Hello", new NotSerializableEncoding()); + serializableSourceText = new SerializableSourceText(sourceText, sourceText.GetContentHash()); using (var stream = SerializableBytes.CreateWritableStream()) { - using var context = new SolutionReplicationContext(); - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(sourceText, objectWriter, context, CancellationToken.None); + serializer.Serialize(serializableSourceText, objectWriter, CancellationToken.None); } stream.Position = 0; using var objectReader = ObjectReader.TryGetReader(stream); - var newText = (SourceText)serializer.Deserialize(sourceText.GetWellKnownSynchronizationKind(), objectReader, CancellationToken.None); - Assert.Equal(sourceText.ToString(), newText.ToString()); + var newText = (SerializableSourceText)serializer.Deserialize(serializableSourceText.GetWellKnownSynchronizationKind(), objectReader, CancellationToken.None); + Assert.Equal(sourceText.ToString(), newText.GetText(CancellationToken.None).ToString()); } } @@ -659,11 +658,9 @@ public void TestCompilationOptions_NullableAndImport() void VerifyOptions(CompilationOptions originalOptions) { using var stream = SerializableBytes.CreateWritableStream(); - using var context = new SolutionReplicationContext(); - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(originalOptions, objectWriter, context, CancellationToken.None); + serializer.Serialize(originalOptions, objectWriter, CancellationToken.None); } stream.Position = 0; @@ -680,17 +677,17 @@ void VerifyOptions(CompilationOptions originalOptions) private static SolutionAsset CloneAsset(ISerializerService serializer, SolutionAsset asset) { using var stream = SerializableBytes.CreateWritableStream(); - using var context = new SolutionReplicationContext(); - using (var writer = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(asset.Value, writer, context, CancellationToken.None); + serializer.Serialize(asset.Value, writer, CancellationToken.None); } stream.Position = 0; using var reader = ObjectReader.TryGetReader(stream); var recovered = serializer.Deserialize(asset.Kind, reader, CancellationToken.None); - var assetFromStorage = new SolutionAsset(serializer.CreateChecksum(recovered, CancellationToken.None), recovered); + var checksum = recovered is SerializableSourceText text ? text.ContentChecksum : serializer.CreateChecksum(recovered, CancellationToken.None); + + var assetFromStorage = new SolutionAsset(checksum, recovered); Assert.Equal(asset.Checksum, assetFromStorage.Checksum); return assetFromStorage; diff --git a/src/VisualStudio/Core/Test.Next/Remote/SolutionAsset.cs b/src/VisualStudio/Core/Test.Next/Remote/SolutionAsset.cs index 379411f01b4d2..fcb3ed325c622 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SolutionAsset.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SolutionAsset.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.Serialization; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -30,11 +29,6 @@ internal readonly struct SolutionAsset public SolutionAsset(Checksum checksum, object value) { var kind = value.GetWellKnownSynchronizationKind(); - // SolutionAsset is not allowed to hold strong references to SourceText. SerializableSourceText is used - // instead to allow data to be released from process address space when it is also held in temporary - // storage. - // https://github.com/dotnet/roslyn/issues/43802 - Contract.ThrowIfTrue(kind is WellKnownSynchronizationKind.SourceText); Checksum = checksum; Kind = kind; diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index 58ed5c28a5928..f9826d7e8aafe 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -7,11 +7,13 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Serialization; @@ -53,11 +55,12 @@ private static async Task TestAssetAsync(object data) var assetSource = new SimpleAssetSource(workspace.Services.GetService(), new Dictionary() { { checksum, data } }); var provider = new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); - var stored = await provider.GetAssetAsync(assetHint: AssetHint.None, checksum, CancellationToken.None); + var stored = await provider.GetAssetAsync(AssetPath.FullLookupForTesting, checksum, CancellationToken.None); Assert.Equal(data, stored); - var stored2 = await provider.GetAssetsAsync(assetHint: AssetHint.None, [checksum], CancellationToken.None); - Assert.Equal(1, stored2.Length); + var stored2 = new List<(Checksum, object)>(); + await provider.GetAssetsAsync(AssetPath.FullLookupForTesting, new HashSet { checksum }, (checksum, asset, _) => stored2.Add((checksum, asset)), default, CancellationToken.None); + Assert.Equal(1, stored2.Count); Assert.Equal(checksum, stored2[0].Item1); Assert.Equal(data, stored2[0].Item2); @@ -74,7 +77,7 @@ public async Task TestAssetSynchronization() // build checksum await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var map = await solution.GetAssetMapAsync(CancellationToken.None); + var map = await solution.GetAssetMapAsync(projectConeId: null, CancellationToken.None); using var remoteWorkspace = CreateRemoteWorkspace(); @@ -83,7 +86,7 @@ public async Task TestAssetSynchronization() var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); var service = new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); - await service.SynchronizeAssetsAsync(assetHint: AssetHint.None, new HashSet(map.Keys), results: null, CancellationToken.None); + await service.GetAssetsAsync(AssetPath.FullLookupForTesting, new HashSet(map.Keys), CancellationToken.None); foreach (var kv in map) { @@ -102,7 +105,7 @@ public async Task TestSolutionSynchronization() // build checksum await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var map = await solution.GetAssetMapAsync(CancellationToken.None); + var map = await solution.GetAssetMapAsync(projectConeId: null, CancellationToken.None); using var remoteWorkspace = CreateRemoteWorkspace(); @@ -136,9 +139,87 @@ public async Task TestProjectSynchronization() var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); var service = new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); - await service.SynchronizeProjectAssetsAsync(await project.State.GetStateChecksumsAsync(CancellationToken.None), CancellationToken.None); + + using var _ = ArrayBuilder.GetInstance(out var allProjectChecksums); + allProjectChecksums.Add(await project.State.GetStateChecksumsAsync(CancellationToken.None)); + + await service.SynchronizeProjectAssetsAsync(allProjectChecksums, CancellationToken.None); TestUtils.VerifyAssetStorage(map, storage); } + + [Fact] + public async Task TestAssetArrayOrdering() + { + var code1 = @"class Test1 { void Method() { } }"; + var code2 = @"class Test2 { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp([code1, code2]); + var project = workspace.CurrentSolution.Projects.First(); + + await project.State.GetChecksumAsync(CancellationToken.None); + + var map = await project.GetAssetMapAsync(CancellationToken.None); + + using var remoteWorkspace = CreateRemoteWorkspace(); + + var sessionId = Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())); + var storage = new SolutionAssetCache(); + var assetSource = new OrderedAssetSource(workspace.Services.GetService(), map); + + var service = new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); + + using var _ = ArrayBuilder.GetInstance(out var allProjectChecksums); + var stateChecksums = await project.State.GetStateChecksumsAsync(CancellationToken.None); + + var textChecksums = stateChecksums.Documents.TextChecksums; + var textChecksumsReversed = new ChecksumCollection(textChecksums.Children.Reverse().ToImmutableArray()); + + var documents = await service.GetAssetsArrayAsync( + AssetPath.FullLookupForTesting, textChecksums, CancellationToken.None); + Assert.True(documents.Length == 2); + + storage.GetTestAccessor().Clear(); + var documentsReversed = await service.GetAssetsArrayAsync( + AssetPath.FullLookupForTesting, textChecksumsReversed, CancellationToken.None); + Assert.True(documentsReversed.Length == 2); + + Assert.True(documents.Select(d => d.ContentChecksum).SequenceEqual(documentsReversed.Reverse().Select(d => d.ContentChecksum))); + } + + private sealed class OrderedAssetSource( + ISerializerService serializerService, + IReadOnlyDictionary map) : IAssetSource + { + public ValueTask GetAssetsAsync( + Checksum solutionChecksum, + AssetPath assetPath, + ReadOnlyMemory checksums, + ISerializerService deserializerService, + Action callback, + TArg arg, + CancellationToken cancellationToken) + { + foreach (var (checksum, asset) in map) + { + if (checksums.Span.IndexOf(checksum) >= 0) + { + using var stream = new MemoryStream(); + using (var writer = new ObjectWriter(stream, leaveOpen: true)) + { + serializerService.Serialize(asset, writer, cancellationToken); + } + + stream.Position = 0; + using var reader = ObjectReader.GetReader(stream, leaveOpen: true); + var deserialized = deserializerService.Deserialize(asset.GetWellKnownSynchronizationKind(), reader, cancellationToken); + Contract.ThrowIfNull(deserialized); + callback(checksum, (T)deserialized, arg); + } + } + + return ValueTaskFactory.CompletedTask; + } + } } } diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 30022d8d436a0..d73b4bb4f6ee9 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -16,7 +16,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.DesignerAttribute; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; @@ -34,7 +33,6 @@ using Roslyn.Test.Utilities.TestGenerators; using Roslyn.Utilities; using Xunit; -using Xunit.Sdk; namespace Roslyn.VisualStudio.Next.UnitTests.Remote { @@ -93,7 +91,7 @@ public async Task TestRemoteHostTextSynchronize() // sync await client.TryInvokeAsync( - (service, cancellationToken) => service.SynchronizeTextAsync(oldDocument.Id, oldState.Text, newText.GetTextChanges(oldText), cancellationToken), + (service, cancellationToken) => service.SynchronizeTextAsync(oldDocument.Id, oldState.Text, newText.GetTextChanges(oldText).AsImmutable(), cancellationToken), CancellationToken.None); // apply change to solution @@ -137,7 +135,7 @@ public async Task TestDesignerAttributes() // Ensure remote workspace is in sync with normal workspace. var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None); + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); var callback = new DesignerAttributeComputerCallback(); @@ -188,7 +186,7 @@ public async Task TestDesignerAttributesUnsupportedLanguage() // Ensure remote workspace is in sync with normal workspace. var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None); + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); var callback = new DesignerAttributeComputerCallback(); @@ -359,9 +357,8 @@ public async Task TestRemoteWorkspaceCircularReferences() using var remoteWorkspace = new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); // this shouldn't throw exception - var (solution, updated) = await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync( - remoteWorkspace.GetTestAccessor().CreateSolutionFromInfo(solutionInfo), workspaceVersion: 1); - Assert.True(updated); + var solution = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync( + remoteWorkspace.GetTestAccessor().CreateSolutionFromInfo(solutionInfo)); Assert.NotNull(solution); } @@ -369,7 +366,7 @@ private static ImmutableArray> Permute(T[] values) { using var _ = ArrayBuilder>.GetInstance(out var result); DoPermute(0, values.Length - 1); - return result.ToImmutable(); + return result.ToImmutableAndClear(); void DoPermute(int start, int end) { @@ -685,6 +682,674 @@ await TestInProcAndRemoteWorkspace( ImmutableArray.Create(("SG.cs", CreateStreamText(contents, useBOM: useBOM2, useMemoryStream: useMemoryStream2)))); } + [PartNotDiscoverable] + [ExportWorkspaceService(typeof(IWorkspaceConfigurationService), ServiceLayer.Test), System.Composition.Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class TestWorkspaceConfigurationService(IGlobalOptionService globalOptionService) : IWorkspaceConfigurationService + { + public WorkspaceConfigurationOptions Options => globalOptionService.GetWorkspaceConfigurationOptions(); + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_RegenerateOnEdit( + SourceGeneratorExecutionPreference executionPreference) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, executionPreference); + + var callCount = 0; + var generator = new CallbackGenerator( + onInit: _ => { }, + onExecute: _ => { }, + computeSourceTexts: () => + { + callCount++; + return ImmutableArray.Create(("hint", SourceText.From($"// generated document {callCount}", Encoding.UTF8))); + }); + + var tempDocId = AddSimpleDocument(workspace, generator); + + using var client = await InProcRemoteHostClient.GetTestClientAsync(workspace).ConfigureAwait(false); + + var workspaceConfigurationService = workspace.Services.GetRequiredService(); + + var remoteProcessId = await client.TryInvokeAsync( + (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, cancellationToken), + CancellationToken.None).ConfigureAwait(false); + + var solution = workspace.CurrentSolution; + await UpdatePrimaryWorkspace(client, solution); + + var project = solution.Projects.Single(); + var compilation = await project.GetCompilationAsync(); + + // Should call the generator the first time. + Assert.Equal(1, callCount); + + solution = solution.WithTextDocumentText(tempDocId, SourceText.From("// new contents")); + Assert.True(workspace.SetCurrentSolution(_ => solution, WorkspaceChangeKind.SolutionChanged)); + + solution = workspace.CurrentSolution; + project = solution.Projects.Single(); + + compilation = await project.GetCompilationAsync(); + + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(); + Assert.Single(sourceGeneratedDocuments); + if (executionPreference is SourceGeneratorExecutionPreference.Automatic) + { + Assert.Equal(2, callCount); + Assert.Equal("// generated document 2", sourceGeneratedDocuments.Single().GetTextSynchronously(CancellationToken.None).ToString()); + } + else + { + Assert.Equal(1, callCount); + Assert.Equal("// generated document 1", sourceGeneratedDocuments.Single().GetTextSynchronously(CancellationToken.None).ToString()); + } + } + + private static DocumentId AddSimpleDocument(TestWorkspace workspace, CallbackGenerator generator) + { + var projectId = ProjectId.CreateNewId(); + var analyzerReference = new TestGeneratorReference(generator); + var project = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId) + .AddAnalyzerReference(analyzerReference); + var tempDoc = project.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + + return tempDoc.Id; + } + + private static IAsynchronousOperationWaiter GetWorkspaceWaiter(TestWorkspace workspace) + { + var operations = workspace.ExportProvider.GetExportedValue(); + return operations.GetWaiter(FeatureAttribute.Workspace); + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_MinorVersionChange_NoActualChange( + SourceGeneratorExecutionPreference executionPreference) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, executionPreference); + + var callCount = 0; + var generator = new CallbackGenerator( + onInit: _ => { }, + onExecute: _ => { }, + computeSourceTexts: () => + { + callCount++; + return ImmutableArray.Create(("hint", SourceText.From($"// generated document {callCount}", Encoding.UTF8))); + }); + + var tempDocId = AddSimpleDocument(workspace, generator); + + using var client = await InProcRemoteHostClient.GetTestClientAsync(workspace).ConfigureAwait(false); + + var workspaceConfigurationService = workspace.Services.GetRequiredService(); + + var remoteProcessId = await client.TryInvokeAsync( + (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, cancellationToken), + CancellationToken.None).ConfigureAwait(false); + + var solution = workspace.CurrentSolution; + await UpdatePrimaryWorkspace(client, solution); + + var project = solution.Projects.Single(); + var compilation = await project.GetCompilationAsync(); + + // Should call the generator the first time. + Assert.Equal(1, callCount); + + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + solution = workspace.CurrentSolution; + project = solution.Projects.Single(); + + compilation = await project.GetCompilationAsync(); + + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(); + Assert.Single(sourceGeneratedDocuments); + + // In both cases, we only expect to be called once. That's because there was no actual change to + // anything else in the compilation. So the generator driver should return only cached data. + Assert.Equal(1, callCount); + Assert.Equal("// generated document 1", sourceGeneratedDocuments.Single().GetTextSynchronously(CancellationToken.None).ToString()); + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_MajorVersionChange_NoActualChange( + SourceGeneratorExecutionPreference executionPreference) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, executionPreference); + + var callCount = 0; + var generator = new CallbackGenerator( + onInit: _ => { }, + onExecute: _ => { }, + computeSourceTexts: () => + { + callCount++; + return ImmutableArray.Create(("hint", SourceText.From($"// generated document {callCount}", Encoding.UTF8))); + }); + + var tempDocId = AddSimpleDocument(workspace, generator); + + using var client = await InProcRemoteHostClient.GetTestClientAsync(workspace).ConfigureAwait(false); + + var workspaceConfigurationService = workspace.Services.GetRequiredService(); + + var remoteProcessId = await client.TryInvokeAsync( + (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, cancellationToken), + CancellationToken.None).ConfigureAwait(false); + + var solution = workspace.CurrentSolution; + await UpdatePrimaryWorkspace(client, solution); + + var project = solution.Projects.Single(); + var compilation = await project.GetCompilationAsync(); + + // Should call the generator the first time. + Assert.Equal(1, callCount); + + // Because we're forcing regeneration, in both mode we should now see two calls to the generator. + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: true); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + solution = workspace.CurrentSolution; + project = solution.Projects.Single(); + + compilation = await project.GetCompilationAsync(); + + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(); + Assert.Single(sourceGeneratedDocuments); + + Assert.Equal(2, callCount); + Assert.Equal("// generated document 2", sourceGeneratedDocuments.Single().GetTextSynchronously(CancellationToken.None).ToString()); + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_FullSolutionChange_Minor(SourceGeneratorExecutionPreference sourceGeneratorExecution) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, sourceGeneratorExecution); + + // want to access the true workspace solution (which will be a fork of the solution we're producing here). + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + { + var project1 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId1); + var tempDoc = project1.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + { + var project2 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId2); + var tempDoc = project2.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + var initialSolution = workspace.CurrentSolution; + + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + var currentSolution = workspace.CurrentSolution; + + if (sourceGeneratorExecution is SourceGeneratorExecutionPreference.Automatic) + { + // In automatic mode, nothing should change. + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + else + { + // In balanced mode, both projects should have their minor version updated. + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1).IncrementMinorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2).IncrementMinorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_FullSolutionChange_Major(SourceGeneratorExecutionPreference sourceGeneratorExecution) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, sourceGeneratorExecution); + + // want to access the true workspace solution (which will be a fork of the solution we're producing here). + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + { + var project1 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId1); + var tempDoc = project1.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + { + var project2 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId2); + var tempDoc = project2.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + var initialSolution = workspace.CurrentSolution; + + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: true); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + var currentSolution = workspace.CurrentSolution; + + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1).IncrementMajorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2).IncrementMajorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_FullSolutionChange_MajorAndMinor(SourceGeneratorExecutionPreference sourceGeneratorExecution) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, sourceGeneratorExecution); + + // want to access the true workspace solution (which will be a fork of the solution we're producing here). + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + { + var project1 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId1); + var tempDoc = project1.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + { + var project2 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId2); + var tempDoc = project2.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + var initialSolution = workspace.CurrentSolution; + + // forceRegeneration=true should take precedence. + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false); + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: true); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + var currentSolution = workspace.CurrentSolution; + + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1).IncrementMajorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2).IncrementMajorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_ProjectChange_Minor_1(SourceGeneratorExecutionPreference sourceGeneratorExecution) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, sourceGeneratorExecution); + + // want to access the true workspace solution (which will be a fork of the solution we're producing here). + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + { + var project1 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId1); + var tempDoc = project1.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + { + var project2 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId2); + var tempDoc = project2.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + var initialSolution = workspace.CurrentSolution; + + // Updating project1 should only impact it. + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: projectId1, forceRegeneration: false); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + var currentSolution = workspace.CurrentSolution; + + if (sourceGeneratorExecution is SourceGeneratorExecutionPreference.Automatic) + { + // In automatic mode, nothing should change. + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + } + else + { + // In balanced mode, only this project should have its minor version changed. + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1).IncrementMinorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + } + + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_ProjectChange_Minor_2(SourceGeneratorExecutionPreference sourceGeneratorExecution) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, sourceGeneratorExecution); + + // want to access the true workspace solution (which will be a fork of the solution we're producing here). + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + { + var project1 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId1); + var tempDoc = project1.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + { + var project2 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId2) + .AddProjectReference(new(projectId1)); + var tempDoc = project2.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + var initialSolution = workspace.CurrentSolution; + + // Updating project1 should regen both projects due to p2p reference. + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: projectId1, forceRegeneration: false); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + var currentSolution = workspace.CurrentSolution; + + if (sourceGeneratorExecution is SourceGeneratorExecutionPreference.Automatic) + { + // In automatic mode, nothing should change. + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + else + { + // In balanced mode, both projects should update their minor version + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1).IncrementMinorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2).IncrementMinorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_ProjectChange_Minor_3(SourceGeneratorExecutionPreference sourceGeneratorExecution) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, sourceGeneratorExecution); + + // want to access the true workspace solution (which will be a fork of the solution we're producing here). + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + { + var project1 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId1); + var tempDoc = project1.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + { + var project2 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId2) + .AddProjectReference(new(projectId1)); + var tempDoc = project2.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + var initialSolution = workspace.CurrentSolution; + + // Updating project2 should regen only it due to project1 having no reference to it. + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: projectId2, forceRegeneration: false); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + var currentSolution = workspace.CurrentSolution; + + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + + if (sourceGeneratorExecution is SourceGeneratorExecutionPreference.Automatic) + { + // In automatic mode, nothing should change. + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + else + { + // In balanced mode, only the requested project should change. + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2).IncrementMinorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_ProjectChange_MinorAndMajor1(SourceGeneratorExecutionPreference sourceGeneratorExecution) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, sourceGeneratorExecution); + + // want to access the true workspace solution (which will be a fork of the solution we're producing here). + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + { + var project1 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId1); + var tempDoc = project1.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + { + var project2 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId2) + .AddProjectReference(new(projectId1)); + var tempDoc = project2.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + var initialSolution = workspace.CurrentSolution; + + // Updating project1 should regen both projects due to p2p reference. Force-regen should take precedence + // for both as project2 has a ref on project1. + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: projectId1, forceRegeneration: true); + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: projectId2, forceRegeneration: false); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + var currentSolution = workspace.CurrentSolution; + + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1).IncrementMajorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2).IncrementMajorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_ProjectChange_MinorAndMajor2(SourceGeneratorExecutionPreference sourceGeneratorExecution) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, sourceGeneratorExecution); + + // want to access the true workspace solution (which will be a fork of the solution we're producing here). + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + { + var project1 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId1); + var tempDoc = project1.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + { + var project2 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId2) + .AddProjectReference(new(projectId1)); + var tempDoc = project2.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + var initialSolution = workspace.CurrentSolution; + + // Updating project1 should regen both projects due to p2p reference. Force-regen should take precedence + // only for project2 as project1 doesn't have a ref on it. + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: projectId1, forceRegeneration: false); + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: projectId2, forceRegeneration: true); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + var currentSolution = workspace.CurrentSolution; + + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1).IncrementMinorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2).IncrementMajorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_SolutionAndProjectChange_1(SourceGeneratorExecutionPreference sourceGeneratorExecution) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, sourceGeneratorExecution); + + // want to access the true workspace solution (which will be a fork of the solution we're producing here). + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + { + var project1 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId1); + var tempDoc = project1.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + { + var project2 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId2); + var tempDoc = project2.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + var initialSolution = workspace.CurrentSolution; + + // Project2 should have a minor update since we only have a solution-minor change. + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false); + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: projectId1, forceRegeneration: true); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + var currentSolution = workspace.CurrentSolution; + + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1).IncrementMajorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2).IncrementMinorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + + [Theory, CombinatorialData] + internal async Task TestSourceGenerationExecution_SolutionAndProjectChange_2(SourceGeneratorExecutionPreference sourceGeneratorExecution) + { + using var workspace = CreateWorkspace([typeof(TestWorkspaceConfigurationService)]); + + var globalOptionService = workspace.ExportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, sourceGeneratorExecution); + + // want to access the true workspace solution (which will be a fork of the solution we're producing here). + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + { + var project1 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId1); + var tempDoc = project1.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + { + var project2 = workspace.CurrentSolution + .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, name: "Test", assemblyName: "Test", language: LanguageNames.CSharp)) + .GetRequiredProject(projectId2); + var tempDoc = project2.AddDocument("X.cs", SourceText.From("// ")); + + Assert.True(workspace.SetCurrentSolution(_ => tempDoc.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + } + + var initialSolution = workspace.CurrentSolution; + + // Project1 and 2 should have a major update since we have a solution-majorchange. + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: true); + workspace.EnqueueUpdateSourceGeneratorVersion(projectId: projectId1, forceRegeneration: false); + await GetWorkspaceWaiter(workspace).ExpeditedWaitAsync(); + + var currentSolution = workspace.CurrentSolution; + + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId1).IncrementMajorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId1)); + Assert.Equal(initialSolution.GetSourceGeneratorExecutionVersion(projectId2).IncrementMajorVersion(), currentSolution.GetSourceGeneratorExecutionVersion(projectId2)); + } + private static async Task VerifyIncrementalUpdatesAsync( TestWorkspace localWorkspace, Workspace remoteWorkspace, @@ -785,7 +1450,7 @@ private static void VerifyStates(Solution solution1, Solution solution2, string private static async Task VerifyAssetStorageAsync(InProcRemoteHostClient client, Solution solution) { - var map = await solution.GetAssetMapAsync(CancellationToken.None); + var map = await solution.GetAssetMapAsync(projectConeId: null, CancellationToken.None); var storage = client.TestData.WorkspaceManager.SolutionAssetCache; @@ -828,10 +1493,9 @@ private static (Project project, ImmutableArray documents) GetProjectA private static async Task UpdatePrimaryWorkspace(RemoteHostClient client, Solution solution) { - var workspaceVersion = solution.WorkspaceVersion; await client.TryInvokeAsync( solution, - async (service, solutionInfo, cancellationToken) => await service.SynchronizePrimaryWorkspaceAsync(solutionInfo, workspaceVersion, cancellationToken), + async (service, solutionInfo, cancellationToken) => await service.SynchronizePrimaryWorkspaceAsync(solutionInfo, cancellationToken), CancellationToken.None); } @@ -861,7 +1525,7 @@ private static Solution Populate(Solution solution) ], [ "cs additional file content" - ], solution.ProjectIds.ToArray()); + ], [.. solution.ProjectIds]); solution = AddProject(solution, LanguageNames.CSharp, [ diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 3f6703acfb73d..93583058aead3 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -13,917 +13,1153 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; -namespace Roslyn.VisualStudio.Next.UnitTests.Remote +namespace Roslyn.VisualStudio.Next.UnitTests.Remote; + +[UseExportProvider] +[Trait(Traits.Feature, Traits.Features.RemoteHost)] +public class SolutionServiceTests { - [UseExportProvider] - [Trait(Traits.Feature, Traits.Features.RemoteHost)] - public class SolutionServiceTests + private static readonly TestComposition s_composition = FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess); + private static readonly TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = + s_composition.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); + + private static RemoteWorkspace CreateRemoteWorkspace() + => new(FeaturesTestCompositions.RemoteHost.GetHostServices()); + + [Fact] + public async Task TestCreation() { - private static RemoteWorkspace CreateRemoteWorkspace() - => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); + var code = @"class Test { void Method() { } }"; - [Fact] - public async Task TestCreation() - { - var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + var solution = workspace.CurrentSolution; + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solution = workspace.CurrentSolution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + } - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - } + [Theory] + [CombinatorialData] + public async Task TestGetSolutionWithPrimaryFlag(bool updatePrimaryBranch) + { + var code1 = @"class Test1 { void Method() { } }"; - [Theory] - [CombinatorialData] - public async Task TestGetSolutionWithPrimaryFlag(bool updatePrimaryBranch) - { - var code1 = @"class Test1 { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code1); + using var remoteWorkspace = CreateRemoteWorkspace(); - using var workspace = TestWorkspace.CreateCSharp(code1); - using var remoteWorkspace = CreateRemoteWorkspace(); + var solution = workspace.CurrentSolution; + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solution = workspace.CurrentSolution; - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, cancellationToken: CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, solution.WorkspaceVersion, cancellationToken: CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(WorkspaceKind.RemoteWorkspace, synched.WorkspaceKind); + } - Assert.Equal(WorkspaceKind.RemoteWorkspace, synched.WorkspaceKind); - } + [Fact] + public async Task TestStrongNameProvider() + { + using var workspace = new AdhocWorkspace(); + using var remoteWorkspace = CreateRemoteWorkspace(); - [Fact] - public async Task TestStrongNameProvider() - { - using var workspace = new AdhocWorkspace(); - using var remoteWorkspace = CreateRemoteWorkspace(); + var filePath = typeof(SolutionServiceTests).Assembly.Location; - var filePath = typeof(SolutionServiceTests).Assembly.Location; + workspace.AddProject( + ProjectInfo.Create( + ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, + filePath: filePath, outputFilePath: filePath)); - workspace.AddProject( - ProjectInfo.Create( - ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, - filePath: filePath, outputFilePath: filePath)); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); + var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); + var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); - var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); + var compilationOptions = solution.Projects.First().CompilationOptions; - var compilationOptions = solution.Projects.First().CompilationOptions; + Assert.IsType(compilationOptions.StrongNameProvider); + Assert.IsType(compilationOptions.XmlReferenceResolver); - Assert.IsType(compilationOptions.StrongNameProvider); - Assert.IsType(compilationOptions.XmlReferenceResolver); + var dirName = PathUtilities.GetDirectoryName(filePath); + var array = new[] { dirName, dirName }; + Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); + Assert.Equal(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory, dirName); + } - var dirName = PathUtilities.GetDirectoryName(filePath); - var array = new[] { dirName, dirName }; - Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); - Assert.Equal(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory, dirName); - } + [Fact] + public async Task TestStrongNameProviderEmpty() + { + using var workspace = new AdhocWorkspace(); + using var remoteWorkspace = CreateRemoteWorkspace(); - [Fact] - public async Task TestStrongNameProviderEmpty() - { - using var workspace = new AdhocWorkspace(); - using var remoteWorkspace = CreateRemoteWorkspace(); + var filePath = "testLocation"; - var filePath = "testLocation"; + workspace.AddProject( + ProjectInfo.Create( + ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, + filePath: filePath, outputFilePath: filePath)); - workspace.AddProject( - ProjectInfo.Create( - ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, - filePath: filePath, outputFilePath: filePath)); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); + var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); + var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); - var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); + var compilationOptions = solution.Projects.First().CompilationOptions; - var compilationOptions = solution.Projects.First().CompilationOptions; + Assert.True(compilationOptions.StrongNameProvider is DesktopStrongNameProvider); + Assert.True(compilationOptions.XmlReferenceResolver is XmlFileResolver); - Assert.True(compilationOptions.StrongNameProvider is DesktopStrongNameProvider); - Assert.True(compilationOptions.XmlReferenceResolver is XmlFileResolver); + var array = new string[] { }; + Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); + Assert.Null(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory); + } - var array = new string[] { }; - Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); - Assert.Null(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory); - } + [Fact] + public async Task TestCache() + { + var code = @"class Test { void Method() { } }"; - [Fact] - public async Task TestCache() - { - var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + var solution = workspace.CurrentSolution; + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var solution = workspace.CurrentSolution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var first = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + var second = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var first = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - var second = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); + // same instance from cache + Assert.True(object.ReferenceEquals(first, second)); + Assert.Equal(WorkspaceKind.RemoteWorkspace, first.WorkspaceKind); + } - // same instance from cache - Assert.True(object.ReferenceEquals(first, second)); - Assert.Equal(WorkspaceKind.RemoteWorkspace, first.WorkspaceKind); - } + [Fact] + public async Task TestUpdatePrimaryWorkspace() + { + var code = @"class Test { void Method() { } }"; - [Fact] - public async Task TestUpdatePrimaryWorkspace() - { - var code = @"class Test { void Method() { } }"; + await VerifySolutionUpdate(code, s => s.WithDocumentText(s.Projects.First().DocumentIds.First(), SourceText.From(code + " "))); + } - await VerifySolutionUpdate(code, s => s.WithDocumentText(s.Projects.First().DocumentIds.First(), SourceText.From(code + " "))); - } + [Fact] + public async Task ProjectProperties() + { + using var workspace = TestWorkspace.CreateCSharp(""); - [Fact] - public async Task ProjectProperties() + static Solution SetProjectProperties(Solution solution, int version) { - using var workspace = TestWorkspace.CreateCSharp(""); - - static Solution SetProjectProperties(Solution solution, int version) - { - var projectId = solution.ProjectIds.Single(); - return solution - .WithProjectName(projectId, "Name" + version) - .WithProjectAssemblyName(projectId, "AssemblyName" + version) - .WithProjectFilePath(projectId, "FilePath" + version) - .WithProjectOutputFilePath(projectId, "OutputFilePath" + version) - .WithProjectOutputRefFilePath(projectId, "OutputRefFilePath" + version) - .WithProjectCompilationOutputInfo(projectId, new CompilationOutputInfo("AssemblyPath" + version)) - .WithProjectDefaultNamespace(projectId, "DefaultNamespace" + version) - .WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1 + version) - .WithHasAllInformation(projectId, (version % 2) != 0) - .WithRunAnalyzers(projectId, (version % 2) != 0); - } - - static void ValidateProperties(Solution solution, int version) - { - var project = solution.Projects.Single(); - Assert.Equal("Name" + version, project.Name); - Assert.Equal("AssemblyName" + version, project.AssemblyName); - Assert.Equal("FilePath" + version, project.FilePath); - Assert.Equal("OutputFilePath" + version, project.OutputFilePath); - Assert.Equal("OutputRefFilePath" + version, project.OutputRefFilePath); - Assert.Equal("AssemblyPath" + version, project.CompilationOutputInfo.AssemblyPath); - Assert.Equal("DefaultNamespace" + version, project.DefaultNamespace); - Assert.Equal(SourceHashAlgorithm.Sha1 + version, project.State.ChecksumAlgorithm); - Assert.Equal((version % 2) != 0, project.State.HasAllInformation); - Assert.Equal((version % 2) != 0, project.State.RunAnalyzers); - } - - Assert.True(workspace.SetCurrentSolution(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged)); - - await VerifySolutionUpdate(workspace, - newSolutionGetter: s => SetProjectProperties(s, version: 1), - oldSolutionValidator: s => ValidateProperties(s, version: 0), - newSolutionValidator: s => ValidateProperties(s, version: 1)).ConfigureAwait(false); + var projectId = solution.ProjectIds.Single(); + return solution + .WithProjectName(projectId, "Name" + version) + .WithProjectAssemblyName(projectId, "AssemblyName" + version) + .WithProjectFilePath(projectId, "FilePath" + version) + .WithProjectOutputFilePath(projectId, "OutputFilePath" + version) + .WithProjectOutputRefFilePath(projectId, "OutputRefFilePath" + version) + .WithProjectCompilationOutputInfo(projectId, new CompilationOutputInfo("AssemblyPath" + version)) + .WithProjectDefaultNamespace(projectId, "DefaultNamespace" + version) + .WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1 + version) + .WithHasAllInformation(projectId, (version % 2) != 0) + .WithRunAnalyzers(projectId, (version % 2) != 0); } - [Fact] - public async Task TestUpdateDocumentInfo() + static void ValidateProperties(Solution solution, int version) { - var code = @"class Test { void Method() { } }"; - - await VerifySolutionUpdate(code, s => s.WithDocumentFolders(s.Projects.First().Documents.First().Id, new[] { "test" })); + var project = solution.Projects.Single(); + Assert.Equal("Name" + version, project.Name); + Assert.Equal("AssemblyName" + version, project.AssemblyName); + Assert.Equal("FilePath" + version, project.FilePath); + Assert.Equal("OutputFilePath" + version, project.OutputFilePath); + Assert.Equal("OutputRefFilePath" + version, project.OutputRefFilePath); + Assert.Equal("AssemblyPath" + version, project.CompilationOutputInfo.AssemblyPath); + Assert.Equal("DefaultNamespace" + version, project.DefaultNamespace); + Assert.Equal(SourceHashAlgorithm.Sha1 + version, project.State.ChecksumAlgorithm); + Assert.Equal((version % 2) != 0, project.State.HasAllInformation); + Assert.Equal((version % 2) != 0, project.State.RunAnalyzers); } - [Fact] - public async Task TestAddUpdateRemoveProjects() + Assert.True(workspace.SetCurrentSolution(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged)); + + await VerifySolutionUpdate(workspace, + newSolutionGetter: s => SetProjectProperties(s, version: 1), + oldSolutionValidator: s => ValidateProperties(s, version: 0), + newSolutionValidator: s => ValidateProperties(s, version: 1)).ConfigureAwait(false); + } + + [Fact] + public async Task TestUpdateDocumentInfo() + { + var code = @"class Test { void Method() { } }"; + + await VerifySolutionUpdate(code, s => s.WithDocumentFolders(s.Projects.First().Documents.First().Id, new[] { "test" })); + } + + [Fact] + public async Task TestAddUpdateRemoveProjects() + { + var code = @"class Test { void Method() { } }"; + + await VerifySolutionUpdate(code, s => { - var code = @"class Test { void Method() { } }"; + var existingProjectId = s.ProjectIds.First(); - await VerifySolutionUpdate(code, s => - { - var existingProjectId = s.ProjectIds.First(); + s = s.AddProject("newProject", "newProject", LanguageNames.CSharp).Solution; - s = s.AddProject("newProject", "newProject", LanguageNames.CSharp).Solution; + var project = s.GetProject(existingProjectId); + project = project.WithCompilationOptions(project.CompilationOptions.WithModuleName("modified")); - var project = s.GetProject(existingProjectId); - project = project.WithCompilationOptions(project.CompilationOptions.WithModuleName("modified")); + var existingDocumentId = project.DocumentIds.First(); - var existingDocumentId = project.DocumentIds.First(); + project = project.AddDocument("newDocument", SourceText.From("// new text")).Project; - project = project.AddDocument("newDocument", SourceText.From("// new text")).Project; + var document = project.GetDocument(existingDocumentId); - var document = project.GetDocument(existingDocumentId); + document = document.WithSourceCodeKind(SourceCodeKind.Script); - document = document.WithSourceCodeKind(SourceCodeKind.Script); + return document.Project.Solution; + }); + } - return document.Project.Solution; - }); - } + [Fact] + public async Task TestAdditionalDocument() + { + var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + + var projectId = workspace.CurrentSolution.ProjectIds.First(); + var additionalDocumentId = DocumentId.CreateNewId(projectId); + var additionalDocumentInfo = DocumentInfo.Create( + additionalDocumentId, "additionalFile", + loader: TextLoader.From(TextAndVersion.Create(SourceText.From("test"), VersionStamp.Create()))); - [Fact] - public async Task TestAdditionalDocument() + await VerifySolutionUpdate(workspace, s => { - var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - - var projectId = workspace.CurrentSolution.ProjectIds.First(); - var additionalDocumentId = DocumentId.CreateNewId(projectId); - var additionalDocumentInfo = DocumentInfo.Create( - additionalDocumentId, "additionalFile", - loader: TextLoader.From(TextAndVersion.Create(SourceText.From("test"), VersionStamp.Create()))); - - await VerifySolutionUpdate(workspace, s => - { - return s.AddAdditionalDocument(additionalDocumentInfo); - }); - - workspace.OnAdditionalDocumentAdded(additionalDocumentInfo); - - await VerifySolutionUpdate(workspace, s => - { - return s.WithAdditionalDocumentText(additionalDocumentId, SourceText.From("changed")); - }); - - await VerifySolutionUpdate(workspace, s => - { - return s.RemoveAdditionalDocument(additionalDocumentId); - }); - } + return s.AddAdditionalDocument(additionalDocumentInfo); + }); + + workspace.OnAdditionalDocumentAdded(additionalDocumentInfo); - [Fact] - public async Task TestAnalyzerConfigDocument() + await VerifySolutionUpdate(workspace, s => { - var configPath = Path.Combine(Path.GetTempPath(), ".editorconfig"); - var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - - var projectId = workspace.CurrentSolution.ProjectIds.First(); - var analyzerConfigDocumentId = DocumentId.CreateNewId(projectId); - var analyzerConfigDocumentInfo = DocumentInfo.Create( - analyzerConfigDocumentId, - name: ".editorconfig", - loader: TextLoader.From(TextAndVersion.Create(SourceText.From("root = true"), VersionStamp.Create(), filePath: configPath)), - filePath: configPath); - - await VerifySolutionUpdate(workspace, s => - { - return s.AddAnalyzerConfigDocuments(ImmutableArray.Create(analyzerConfigDocumentInfo)); - }); - - workspace.OnAnalyzerConfigDocumentAdded(analyzerConfigDocumentInfo); - - await VerifySolutionUpdate(workspace, s => - { - return s.WithAnalyzerConfigDocumentText(analyzerConfigDocumentId, SourceText.From("root = false")); - }); - - await VerifySolutionUpdate(workspace, s => - { - return s.RemoveAnalyzerConfigDocument(analyzerConfigDocumentId); - }); - } + return s.WithAdditionalDocumentText(additionalDocumentId, SourceText.From("changed")); + }); - [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] - public async Task TestDocument() + await VerifySolutionUpdate(workspace, s => { - var code = @"class Test { void Method() { } }"; + return s.RemoveAdditionalDocument(additionalDocumentId); + }); + } + + [Fact] + public async Task TestAnalyzerConfigDocument() + { + var configPath = Path.Combine(Path.GetTempPath(), ".editorconfig"); + var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + + var projectId = workspace.CurrentSolution.ProjectIds.First(); + var analyzerConfigDocumentId = DocumentId.CreateNewId(projectId); + var analyzerConfigDocumentInfo = DocumentInfo.Create( + analyzerConfigDocumentId, + name: ".editorconfig", + loader: TextLoader.From(TextAndVersion.Create(SourceText.From("root = true"), VersionStamp.Create(), filePath: configPath)), + filePath: configPath); + + await VerifySolutionUpdate(workspace, s => + { + return s.AddAnalyzerConfigDocuments(ImmutableArray.Create(analyzerConfigDocumentInfo)); + }); - using var workspace = TestWorkspace.CreateCSharp(code); + workspace.OnAnalyzerConfigDocumentAdded(analyzerConfigDocumentInfo); - var projectId = workspace.CurrentSolution.ProjectIds.First(); - var documentId = DocumentId.CreateNewId(projectId); - var documentInfo = DocumentInfo.Create( - documentId, "sourceFile", - loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class A { }"), VersionStamp.Create()))); + await VerifySolutionUpdate(workspace, s => + { + return s.WithAnalyzerConfigDocumentText(analyzerConfigDocumentId, SourceText.From("root = false")); + }); - await VerifySolutionUpdate(workspace, s => - { - return s.AddDocument(documentInfo); - }); + await VerifySolutionUpdate(workspace, s => + { + return s.RemoveAnalyzerConfigDocument(analyzerConfigDocumentId); + }); + } - workspace.OnDocumentAdded(documentInfo); + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public async Task TestDocument() + { + var code = @"class Test { void Method() { } }"; - await VerifySolutionUpdate(workspace, s => - { - return s.WithDocumentText(documentId, SourceText.From("class Changed { }")); - }); + using var workspace = TestWorkspace.CreateCSharp(code); - await VerifySolutionUpdate(workspace, s => - { - return s.RemoveDocument(documentId); - }); - } + var projectId = workspace.CurrentSolution.ProjectIds.First(); + var documentId = DocumentId.CreateNewId(projectId); + var documentInfo = DocumentInfo.Create( + documentId, "sourceFile", + loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class A { }"), VersionStamp.Create()))); - [Fact] - public async Task TestRemoteWorkspace() + await VerifySolutionUpdate(workspace, s => { - var code = @"class Test { void Method() { } }"; + return s.AddDocument(documentInfo); + }); - // create base solution - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + workspace.OnDocumentAdded(documentInfo); - // create solution service - var solution1 = workspace.CurrentSolution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution1); + await VerifySolutionUpdate(workspace, s => + { + return s.WithDocumentText(documentId, SourceText.From("class Changed { }")); + }); - var remoteSolution1 = await GetInitialOOPSolutionAsync(remoteWorkspace, assetProvider, solution1); + await VerifySolutionUpdate(workspace, s => + { + return s.RemoveDocument(documentId); + }); + } - await Verify(remoteWorkspace, solution1, remoteSolution1, expectRemoteSolutionToCurrent: true); - var version = solution1.WorkspaceVersion; + [Fact] + public async Task TestRemoteWorkspace() + { + var code = @"class Test { void Method() { } }"; - // update remote workspace - var currentSolution = remoteSolution1.WithDocumentText(remoteSolution1.Projects.First().Documents.First().Id, SourceText.From(code + " class Test2 { }")); - var (oopSolution2, _) = await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync(currentSolution, ++version); + // create base solution + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - await Verify(remoteWorkspace, currentSolution, oopSolution2, expectRemoteSolutionToCurrent: true); + // create solution service + var solution1 = workspace.CurrentSolution; + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution1); - // move backward - await Verify(remoteWorkspace, remoteSolution1, (await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync(remoteSolution1, solution1.WorkspaceVersion)).solution, expectRemoteSolutionToCurrent: false); + var remoteSolution1 = await GetInitialOOPSolutionAsync(remoteWorkspace, assetProvider, solution1); - // move forward - currentSolution = oopSolution2.WithDocumentText(oopSolution2.Projects.First().Documents.First().Id, SourceText.From(code + " class Test3 { }")); - var remoteSolution3 = (await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync(currentSolution, ++version)).solution; + await Verify(remoteWorkspace, solution1, remoteSolution1); - await Verify(remoteWorkspace, currentSolution, remoteSolution3, expectRemoteSolutionToCurrent: true); + // update remote workspace + var currentSolution = remoteSolution1.WithDocumentText(remoteSolution1.Projects.First().Documents.First().Id, SourceText.From(code + " class Test2 { }")); + var oopSolution2 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(currentSolution); - // move to new solution backward - var solutionInfo2 = await assetProvider.CreateSolutionInfoAsync(await solution1.CompilationState.GetChecksumAsync(CancellationToken.None), CancellationToken.None); - var solution2 = remoteWorkspace.GetTestAccessor().CreateSolutionFromInfo(solutionInfo2); - Assert.False((await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync( - solution2, solution1.WorkspaceVersion)).updated); + await Verify(remoteWorkspace, currentSolution, oopSolution2); - // move to new solution forward - var (solution3, updated3) = await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync( - solution2, ++version); - Assert.NotNull(solution3); - Assert.True(updated3); - await Verify(remoteWorkspace, solution1, solution3, expectRemoteSolutionToCurrent: true); + // move backward + await Verify(remoteWorkspace, remoteSolution1, await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(remoteSolution1)); - static async Task GetInitialOOPSolutionAsync(RemoteWorkspace remoteWorkspace, AssetProvider assetProvider, Solution solution) - { - // set up initial solution - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None); + // move forward + currentSolution = oopSolution2.WithDocumentText(oopSolution2.Projects.First().Documents.First().Id, SourceText.From(code + " class Test3 { }")); + var remoteSolution3 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(currentSolution); - // get solution in remote host - return await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - } + await Verify(remoteWorkspace, currentSolution, remoteSolution3); - static async Task Verify(RemoteWorkspace remoteWorkspace, Solution givenSolution, Solution remoteSolution, bool expectRemoteSolutionToCurrent) - { - // verify we got solution expected - Assert.Equal(await givenSolution.CompilationState.GetChecksumAsync(CancellationToken.None), await remoteSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + // move to new solution backward + var solutionInfo2 = await assetProvider.CreateSolutionInfoAsync(await solution1.CompilationState.GetChecksumAsync(CancellationToken.None), CancellationToken.None); + var solution2 = remoteWorkspace.GetTestAccessor().CreateSolutionFromInfo(solutionInfo2); - // verify remote workspace got updated - Assert.True(expectRemoteSolutionToCurrent == (remoteSolution == remoteWorkspace.CurrentSolution)); - } - } + // move to new solution forward + var solution3 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(solution2); + Assert.NotNull(solution3); + await Verify(remoteWorkspace, solution1, solution3); - [Theory, CombinatorialData] - [WorkItem("https://github.com/dotnet/roslyn/issues/48564")] - public async Task TestAddingProjectsWithExplicitOptions(bool useDefaultOptionValue) + static async Task GetInitialOOPSolutionAsync(RemoteWorkspace remoteWorkspace, AssetProvider assetProvider, Solution solution) { - using var workspace = TestWorkspace.CreateCSharp(@"public class C { }"); - using var remoteWorkspace = CreateRemoteWorkspace(); - - // Initial empty solution - var solution = workspace.CurrentSolution; - solution = solution.RemoveProject(solution.ProjectIds.Single()); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + // set up initial solution var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 0, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - - // Add a C# project and a VB project, set some options, and check again - var csharpDocument = new TestHostDocument("public class C { }"); - var csharpProject = new TestHostProject(workspace, csharpDocument, language: LanguageNames.CSharp, name: "project2"); - var csharpProjectInfo = csharpProject.ToProjectInfo(); - - var vbDocument = new TestHostDocument("Public Class D \r\n Inherits C\r\nEnd Class"); - var vbProject = new TestHostProject(workspace, vbDocument, language: LanguageNames.VisualBasic, name: "project3"); - var vbProjectInfo = vbProject.ToProjectInfo(); - - solution = solution.AddProject(csharpProjectInfo).AddProject(vbProjectInfo); - var newOptionValue = useDefaultOptionValue - ? FormattingOptions2.NewLine.DefaultValue - : FormattingOptions2.NewLine.DefaultValue + FormattingOptions2.NewLine.DefaultValue; - solution = solution.WithOptions(solution.Options - .WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, newOptionValue) - .WithChangedOption(FormattingOptions.NewLine, LanguageNames.VisualBasic, newOptionValue)); - - assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 2, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); + + // get solution in remote host + return await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); } - [Fact] - public async Task TestFrozenSourceGeneratedDocument() + static async Task Verify(RemoteWorkspace remoteWorkspace, Solution givenSolution, Solution remoteSolution) { - using var workspace = TestWorkspace.CreateCSharp(@""); - using var remoteWorkspace = CreateRemoteWorkspace(); - - var solution = workspace.CurrentSolution - .Projects.Single() - .AddAnalyzerReference(new AnalyzerFileReference(typeof(Microsoft.CodeAnalysis.TestSourceGenerator.HelloWorldGenerator).Assembly.Location, new TestAnalyzerAssemblyLoader())) - .Solution; + // verify we got solution expected + Assert.Equal(await givenSolution.CompilationState.GetChecksumAsync(CancellationToken.None), await remoteSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - // First sync the solution over that has a generator - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 0, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - - // Now freeze with some content - var documentIdentity = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).First().Identity; - var frozenText1 = SourceText.From("// Hello, World!"); - var frozenSolution1 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, frozenText1).Project.Solution; - - assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution1); - solutionChecksum = await frozenSolution1.CompilationState.GetChecksumAsync(CancellationToken.None); - synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 1, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - - // Try freezing with some different content from the original solution - var frozenText2 = SourceText.From("// Hello, World! A second time!"); - var frozenSolution2 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, frozenText2).Project.Solution; - - assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution2); - solutionChecksum = await frozenSolution2.CompilationState.GetChecksumAsync(CancellationToken.None); - synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 2, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + // verify remote workspace got updated + Assert.Equal(remoteSolution, remoteWorkspace.CurrentSolution); } + } - [Fact] - public async Task TestPartialProjectSync_GetSolutionFirst() - { - var code = @"class Test { void Method() { } }"; + [Theory, CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/48564")] + public async Task TestAddingProjectsWithExplicitOptions(bool useDefaultOptionValue) + { + using var workspace = TestWorkspace.CreateCSharp(@"public class C { }"); + using var remoteWorkspace = CreateRemoteWorkspace(); + + // Initial empty solution + var solution = workspace.CurrentSolution; + solution = solution.RemoveProject(solution.ProjectIds.Single()); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // Add a C# project and a VB project, set some options, and check again + var csharpDocument = new TestHostDocument("public class C { }"); + var csharpProject = new TestHostProject(workspace, csharpDocument, language: LanguageNames.CSharp, name: "project2"); + var csharpProjectInfo = csharpProject.ToProjectInfo(); + + var vbDocument = new TestHostDocument("Public Class D \r\n Inherits C\r\nEnd Class"); + var vbProject = new TestHostProject(workspace, vbDocument, language: LanguageNames.VisualBasic, name: "project3"); + var vbProjectInfo = vbProject.ToProjectInfo(); + + solution = solution.AddProject(csharpProjectInfo).AddProject(vbProjectInfo); + var newOptionValue = useDefaultOptionValue + ? FormattingOptions2.NewLine.DefaultValue + : FormattingOptions2.NewLine.DefaultValue + FormattingOptions2.NewLine.DefaultValue; + solution = solution.WithOptions(solution.Options + .WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, newOptionValue) + .WithChangedOption(FormattingOptions.NewLine, LanguageNames.VisualBasic, newOptionValue)); + + assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + } - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + [Fact] + public async Task TestFrozenSourceGeneratedDocument() + { + using var workspace = TestWorkspace.CreateCSharp(@""); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution + .Projects.Single() + .AddAnalyzerReference(new AnalyzerFileReference(typeof(Microsoft.CodeAnalysis.TestSourceGenerator.HelloWorldGenerator).Assembly.Location, new TestAnalyzerAssemblyLoader())) + .Solution; + + // First sync the solution over that has a generator + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // Now freeze with some content + var documentIdentity = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).First().Identity; + var frozenText1 = SourceText.From("// Hello, World!"); + var frozenSolution1 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, DateTime.Now, frozenText1).Project.Solution; + + assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution1); + solutionChecksum = await frozenSolution1.CompilationState.GetChecksumAsync(CancellationToken.None); + synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // Try freezing with some different content from the original solution + var frozenText2 = SourceText.From("// Hello, World! A second time!"); + var frozenSolution2 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, DateTime.Now, frozenText2).Project.Solution; + + assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution2); + solutionChecksum = await frozenSolution2.CompilationState.GetChecksumAsync(CancellationToken.None); + synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + } - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_GetSolutionFirst() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project2.Solution; + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - // Do the initial full sync - await solution.AppendAssetMapAsync(map, CancellationToken.None); + solution = project2.Solution; - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: solution.WorkspaceVersion, CancellationToken.None); + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - Assert.Equal(2, syncedFullSolution.Projects.Count()); + // Do the initial full sync + await solution.AppendAssetMapAsync(map, CancellationToken.None); - // Syncing project1 should do nothing as syncing the solution already synced it over. - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(2, project1SyncedSolution.Projects.Count()); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - // Syncing project2 should do nothing as syncing the solution already synced it over. - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(2, project2SyncedSolution.Projects.Count()); - } + Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(2, syncedFullSolution.Projects.Count()); - [Fact] - public async Task TestPartialProjectSync_GetSolutionLast() - { - var code = @"class Test { void Method() { } }"; + // Syncing project1 should do nothing as syncing the solution already synced it over. + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project1SyncedSolution.Projects.Count()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // Syncing project2 should do nothing as syncing the solution already synced it over. + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project2SyncedSolution.Projects.Count()); + } - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_GetSolutionLast() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project2.Solution; + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - // Syncing project 1 should just since it over. - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + solution = project2.Solution; - // Syncing project 2 should end up with only p2 synced over. - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - // then syncing the whole project should now copy both over. - await solution.AppendAssetMapAsync(map, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: solution.WorkspaceVersion, CancellationToken.None); + // Syncing project 1 should just since it over. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); - Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - Assert.Equal(2, syncedFullSolution.Projects.Count()); - } + // Syncing project 2 should end up with only p2 synced over. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects1() - { - var code = @"class Test { void Method() { } }"; + // then syncing the whole project should now copy both over. + await solution.AppendAssetMapAsync(map, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(2, syncedFullSolution.Projects.Count()); + } - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects1() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); - Assert.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); - - // syncing project 3 should sync project 2 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(2, project3SyncedSolution.Projects.Count()); - } + solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects2() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); + Assert.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); - var solution = workspace.CurrentSolution; + // syncing project 3 should sync project 2 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project3SyncedSolution.Projects.Count()); + } - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects2() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + + solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); + + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + + // syncing P3 should since project P2 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project3SyncedSolution.Projects.Count()); + + // if we then sync just P2, we should still have only P2 in the synced cone + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); + AssertEx.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); + + // if we then sync just P1, we should only have it in its own cone. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + AssertEx.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + } - solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects3() + { + var code = @"class Test { void Method() { } }"; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - // syncing P3 should since project P2 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(2, project3SyncedSolution.Projects.Count()); + var solution = workspace.CurrentSolution; - // if we then sync just P2, we should still have only P2 in the synced cone - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); - AssertEx.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); - // if we then sync just P1, we should only have it in its own cone. - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - AssertEx.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); - } + solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) + .AddProjectReference(project2.Id, new(project1.Id)); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects3() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // syncing project3 should since project2 and project1 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(3, project3SyncedSolution.Projects.Count()); - var solution = workspace.CurrentSolution; + // syncing project2 should only have it and project 1. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project2SyncedSolution.Projects.Count()); - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + // syncing project1 should only be itself + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + } - solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) - .AddProjectReference(project2.Id, new(project1.Id)); + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects4() + { + var code = @"class Test { void Method() { } }"; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - // syncing project3 should since project2 and project1 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(3, project3SyncedSolution.Projects.Count()); + var solution = workspace.CurrentSolution; - // syncing project2 should only have it and project 1. - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(2, project2SyncedSolution.Projects.Count()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); - // syncing project1 should only be itself - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - } + solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) + .AddProjectReference(project3.Id, new(project1.Id)); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects4() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // syncing project3 should since project2 and project1 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(3, project3SyncedSolution.Projects.Count()); - var solution = workspace.CurrentSolution; + // Syncing project2 should only have a cone with itself. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + // Syncing project1 should only have a cone with itself. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + } - solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) - .AddProjectReference(project3.Id, new(project1.Id)); + [Fact] + public async Task TestPartialProjectSync_Options1() + { + var code = @"class Test { void Method() { } }"; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - // syncing project3 should since project2 and project1 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(3, project3SyncedSolution.Projects.Count()); + var solution = workspace.CurrentSolution; - // Syncing project2 should only have a cone with itself. - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); + + solution = project2.Solution; + + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + + // Syncing over project1 should give us 1 set of options on the OOP side. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + + // Syncing over project2 should also only be one set of options. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); + } + + [Fact] + public async Task TestPartialProjectSync_DoesNotSeeChangesOutsideOfCone() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); + + solution = project2.Solution; + + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + + // Do the initial full sync + await solution.AppendAssetMapAsync(map, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(2, fullSyncedSolution.Projects.Count()); + + // Mutate both projects to each have a document in it. + solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; + solution = solution.GetProject(project2.Id).AddDocument("Y.vb", SourceText.From("' Y")).Project.Solution; - // Syncing project1 should only have a cone with itself. + // Now just sync project1's cone over. We should not see the change to project2 on the remote side. + // But we will still see project2. + { await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project1SyncedSolution.Projects.Count()); + var csharpProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); + var vbProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); + Assert.True(csharpProject.DocumentIds.Count == 2); + Assert.Empty(vbProject.DocumentIds); } - [Fact] - public async Task TestPartialProjectSync_Options1() + // Similarly, if we sync just project2's cone over: { - var code = @"class Test { void Method() { } }"; + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project2SyncedSolution.Projects.Count()); + var csharpProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); + var vbProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); + Assert.Single(csharpProject.DocumentIds); + Assert.Single(vbProject.DocumentIds); + } + } - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + [Fact] + public async Task TestPartialProjectSync_AddP2PRef() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; - var solution = workspace.CurrentSolution; + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); + solution = project2.Solution; - solution = project2.Solution; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + // Do the initial full sync + await solution.AppendAssetMapAsync(map, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(2, fullSyncedSolution.Projects.Count()); - // Syncing over project1 should give us 1 set of options on the OOP side. + // Mutate both projects to have a document in it, and add a p2p ref from project1 to project2 + solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; + solution = solution.GetProject(project2.Id).AddDocument("Y.cs", SourceText.From("// Y")).Project.Solution; + solution = solution.GetProject(project1.Id).AddProjectReference(new ProjectReference(project2.Id)).Solution; + + // Now just sync project1's cone over. This will validate that the p2p ref doesn't try to add a new + // project, but instead sees the existing one. + { await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project1SyncedSolution.Projects.Count()); + var project1Synced = project1SyncedSolution.GetRequiredProject(project1.Id); + var project2Synced = project1SyncedSolution.GetRequiredProject(project2.Id); - // Syncing over project2 should also only be one set of options. - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); + Assert.True(project1Synced.DocumentIds.Count == 2); + Assert.Single(project2Synced.DocumentIds); + Assert.Single(project1Synced.ProjectReferences); } + } - [Fact] - public async Task TestPartialProjectSync_DoesNotSeeChangesOutsideOfCone() - { - var code = @"class Test { void Method() { } }"; + [Fact] + public async Task TestPartialProjectSync_ReferenceToNonExistentProject() + { + var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - var solution = workspace.CurrentSolution; + var solution = workspace.CurrentSolution; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); + var project1 = solution.Projects.Single(); - solution = project2.Solution; + // This reference a project that doesn't exist. + // Ensure that it's still fine to get the checksum for this project we have. + project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + solution = project1.Solution; - // Do the initial full sync - await solution.AppendAssetMapAsync(map, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: solution.WorkspaceVersion, CancellationToken.None); - Assert.Equal(2, fullSyncedSolution.Projects.Count()); - - // Mutate both projects to each have a document in it. - solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; - solution = solution.GetProject(project2.Id).AddDocument("Y.vb", SourceText.From("' Y")).Project.Solution; - - // Now just sync project1's cone over. We should not see the change to project2 on the remote side. - // But we will still see project2. - { - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(2, project1SyncedSolution.Projects.Count()); - var csharpProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); - var vbProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); - Assert.True(csharpProject.DocumentIds.Count == 2); - Assert.Empty(vbProject.DocumentIds); - } - - // Similarly, if we sync just project2's cone over: - { - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(2, project2SyncedSolution.Projects.Count()); - var csharpProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); - var vbProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); - Assert.Single(csharpProject.DocumentIds); - Assert.Single(vbProject.DocumentIds); - } - } + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - [Fact] - public async Task TestPartialProjectSync_AddP2PRef() - { - var code = @"class Test { void Method() { } }"; + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + } - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + [Fact] + public void TestNoActiveDocumentSemanticModelNotCached() + { + var code = @"class Test { void Method() { } }"; - var solution = workspace.CurrentSolution; + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var solution = workspace.CurrentSolution; - solution = project2.Solution; + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + // Without anything holding onto the semantic model, it should get releases. + var objectReference = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); - // Do the initial full sync - await solution.AppendAssetMapAsync(map, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: solution.WorkspaceVersion, CancellationToken.None); - Assert.Equal(2, fullSyncedSolution.Projects.Count()); - - // Mutate both projects to have a document in it, and add a p2p ref from project1 to project2 - solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; - solution = solution.GetProject(project2.Id).AddDocument("Y.cs", SourceText.From("// Y")).Project.Solution; - solution = solution.GetProject(project1.Id).AddProjectReference(new ProjectReference(project2.Id)).Solution; - - // Now just sync project1's cone over. This will validate that the p2p ref doesn't try to add a new - // project, but instead sees the existing one. - { - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(2, project1SyncedSolution.Projects.Count()); - var project1Synced = project1SyncedSolution.GetRequiredProject(project1.Id); - var project2Synced = project1SyncedSolution.GetRequiredProject(project2.Id); - - Assert.True(project1Synced.DocumentIds.Count == 2); - Assert.Single(project2Synced.DocumentIds); - Assert.Single(project1Synced.ProjectReferences); - } - } + objectReference.AssertReleased(); + } - [Fact] - public async Task TestPartialProjectSync_ReferenceToNonExistentProject() - { - var code = @"class Test { void Method() { } }"; + [Fact] + public void TestActiveDocumentSemanticModelCached() + { + var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); - var solution = workspace.CurrentSolution; + var solution = workspace.CurrentSolution; - var project1 = solution.Projects.Single(); + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); - // This reference a project that doesn't exist. - // Ensure that it's still fine to get the checksum for this project we have. - project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); + // Since this is the active document, we should hold onto it. + var objectReference = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); - solution = project1.Solution; + objectReference.AssertHeld(); + } - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + [Fact] + public void TestOnlyActiveDocumentSemanticModelCached() + { + using var workspace = TestWorkspace.Create(""" + + + + class Program1 + { + } + + + class Program2 + { + } + + + + """, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.First(); + var document2 = project1.Documents.Last(); + + // Only the semantic model for the active document should be cached. + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + var objectReference2 = ObjectReference.CreateFromFactory(() => document2.GetSemanticModelAsync().GetAwaiter().GetResult()); + + objectReference1.AssertHeld(); + objectReference2.AssertReleased(); + } - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - } + [Fact] + public async Task TestRemoteWorkspaceCachesNothingIfActiveDocumentNotSynced() + { + var code = @"class Test { void Method() { } }"; - private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) - { - using var workspace = TestWorkspace.CreateCSharp(code); - await VerifySolutionUpdate(workspace, newSolutionGetter); - } + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); - private static async Task VerifySolutionUpdate( - TestWorkspace workspace, - Func newSolutionGetter, - Action oldSolutionValidator = null, - Action newSolutionValidator = null) - { - var solution = workspace.CurrentSolution; - oldSolutionValidator?.Invoke(solution); + var solution = workspace.CurrentSolution; - var map = new Dictionary(); + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); - using var remoteWorkspace = CreateRemoteWorkspace(); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution, map); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + // Locally the semantic model will be held + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1.AssertHeld(); - // update primary workspace - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None); - var recoveredSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - oldSolutionValidator?.Invoke(recoveredSolution); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - Assert.Equal(WorkspaceKind.RemoteWorkspace, recoveredSolution.WorkspaceKind); - Assert.Equal(solutionChecksum, await recoveredSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - // get new solution - var newSolution = newSolutionGetter(solution); - var newSolutionChecksum = await newSolution.CompilationState.GetChecksumAsync(CancellationToken.None); - await newSolution.AppendAssetMapAsync(map, CancellationToken.None); + // The remote semantic model will not be held as it doesn't know what the active document is yet. + var objectReference2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference2.AssertReleased(); + } - // get solution without updating primary workspace - var recoveredNewSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); + [Theory, CombinatorialData] + public async Task TestRemoteWorkspaceCachesPropertyIfActiveDocumentIsSynced(bool updatePrimaryBranch) + { + var code = @"class Test { void Method() { } }"; - Assert.Equal(newSolutionChecksum, await recoveredNewSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); - // do same once updating primary workspace - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, newSolutionChecksum, solution.WorkspaceVersion + 1, CancellationToken.None); - var third = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); + var solution = workspace.CurrentSolution; - Assert.Equal(newSolutionChecksum, await third.CompilationState.GetChecksumAsync(CancellationToken.None)); + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); - newSolutionValidator?.Invoke(recoveredNewSolution); - } + // Locally the semantic model will be held + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1.AssertHeld(); - private static async Task GetAssetProviderAsync(Workspace workspace, RemoteWorkspace remoteWorkspace, Solution solution, Dictionary map = null) - { - // make sure checksum is calculated - await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var remoteDocumentTrackingService = (RemoteDocumentTrackingService)remoteWorkspace.Services.GetRequiredService(); + remoteDocumentTrackingService.SetActiveDocument(document1.Id); - map ??= []; - await solution.AppendAssetMapAsync(map, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, CancellationToken.None); - var sessionId = Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())); - var storage = new SolutionAssetCache(); - var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); + // The remote semantic model will be held as it refers to the active document. + var objectReference2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference2.AssertHeld(); + } - return new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); - } + [Theory, CombinatorialData] + public async Task ValidateUpdaterInformsRemoteWorkspaceOfActiveDocument(bool updatePrimaryBranch) + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + // Locally the semantic model will be held + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1.AssertHeld(); + + // By creating a checksum updater, we should notify the remote workspace of the active document. + var listenerProvider = workspace.ExportProvider.GetExportedValue(); + var checksumUpdater = new SolutionChecksumUpdater(workspace, listenerProvider, CancellationToken.None); + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, CancellationToken.None); + + var waiter = listenerProvider.GetWaiter(FeatureAttribute.SolutionChecksumUpdater); + await waiter.ExpeditedWaitAsync(); + + // The remote semantic model will be held as it refers to the active document. + var objectReference2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference2.AssertHeld(); + } + + [Theory, CombinatorialData] + public async Task ValidateUpdaterInformsRemoteWorkspaceOfActiveDocument_EvenAcrossActiveDocumentChanges(bool updatePrimaryBranch) + { + using var workspace = TestWorkspace.Create(""" + + + + class Program1 + { + } + + + class Program2 + { + } + + + + """, composition: s_composition.AddParts(typeof(TestDocumentTrackingService))); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.First(); + var document2 = project1.Documents.Last(); + + // By creating a checksum updater, we should notify the remote workspace of the active document. Have it + // initially be set to the first document. + var documentTrackingService = (TestDocumentTrackingService)workspace.Services.GetRequiredService(); + documentTrackingService.SetActiveDocument(document1.Id); + + // Locally the semantic model for the first document will be held, but the second will not. + var objectReference1_step1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + var objectReference2_step1 = ObjectReference.CreateFromFactory(() => document2.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1_step1.AssertHeld(); + objectReference2_step1.AssertReleased(); + + var listenerProvider = workspace.ExportProvider.GetExportedValue(); + var checksumUpdater = new SolutionChecksumUpdater(workspace, listenerProvider, CancellationToken.None); + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, CancellationToken.None); + + var waiter = listenerProvider.GetWaiter(FeatureAttribute.SolutionChecksumUpdater); + await waiter.ExpeditedWaitAsync(); + + // The remote semantic model should match the local behavior once it has been notified that the first document is active. + var oopDocumentReference1_step1 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + var oopDocumentReference2_step1 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document2.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + oopDocumentReference1_step1.AssertHeld(); + oopDocumentReference2_step1.AssertReleased(); + + // Now, change the active document to the second document. + documentTrackingService.SetActiveDocument(document2.Id); + + // And get the semantic models again. The second document should now be held, and the first released. + var objectReference1_step2 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + var objectReference2_step2 = ObjectReference.CreateFromFactory(() => document2.GetSemanticModelAsync().GetAwaiter().GetResult()); + + // The second document should be held. + objectReference2_step2.AssertHeld(); + + // Ensure that the active doc change is sync'ed to oop. + await waiter.ExpeditedWaitAsync(); + + // And get the semantic models again on the oop side. The second document should now be held, and the first released. + var oopDocumentReference1_step2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + var oopDocumentReference2_step2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document2.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + + // The second document on oop should now be held. + oopDocumentReference2_step2.AssertHeld(); + } + + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) + { + using var workspace = TestWorkspace.CreateCSharp(code); + await VerifySolutionUpdate(workspace, newSolutionGetter); + } + + private static async Task VerifySolutionUpdate( + TestWorkspace workspace, + Func newSolutionGetter, + Action oldSolutionValidator = null, + Action newSolutionValidator = null) + { + var solution = workspace.CurrentSolution; + oldSolutionValidator?.Invoke(solution); + + var map = new Dictionary(); + + using var remoteWorkspace = CreateRemoteWorkspace(); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution, map); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + + // update primary workspace + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); + var recoveredSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + oldSolutionValidator?.Invoke(recoveredSolution); + + Assert.Equal(WorkspaceKind.RemoteWorkspace, recoveredSolution.WorkspaceKind); + Assert.Equal(solutionChecksum, await recoveredSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // get new solution + var newSolution = newSolutionGetter(solution); + var newSolutionChecksum = await newSolution.CompilationState.GetChecksumAsync(CancellationToken.None); + await newSolution.AppendAssetMapAsync(map, CancellationToken.None); + + // get solution without updating primary workspace + var recoveredNewSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + + Assert.Equal(newSolutionChecksum, await recoveredNewSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // do same once updating primary workspace + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, newSolutionChecksum, CancellationToken.None); + var third = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + + Assert.Equal(newSolutionChecksum, await third.CompilationState.GetChecksumAsync(CancellationToken.None)); + + newSolutionValidator?.Invoke(recoveredNewSolution); + } + + private static async Task GetAssetProviderAsync(Workspace workspace, RemoteWorkspace remoteWorkspace, Solution solution, Dictionary map = null) + { + // make sure checksum is calculated + await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + + map ??= []; + await solution.AppendAssetMapAsync(map, CancellationToken.None); + + var sessionId = Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())); + var storage = new SolutionAssetCache(); + var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); + + return new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); } } diff --git a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs index 98091183d44f1..4eaec828a85b0 100644 --- a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs @@ -192,11 +192,7 @@ void Method() analyzerReference.GetAnalyzers(project.Language).Where(a => a.GetType() == analyzerType).ToImmutableArray(), new WorkspaceAnalyzerOptions(project.AnalyzerOptions, ideAnalyzerOptions)); - // no result for open file only analyzer unless forced - var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, forceExecuteAllAnalyzers: false, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); - Assert.Empty(result.AnalysisResult); - - result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, forceExecuteAllAnalyzers: true, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); + var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); var analyzerResult = result.AnalysisResult[compilationWithAnalyzers.Analyzers[0]]; // check result @@ -234,7 +230,7 @@ void Method() var compilationWithAnalyzers = (await project.GetCompilationAsync()) .WithAnalyzers(analyzers, new WorkspaceAnalyzerOptions(project.AnalyzerOptions, ideAnalyzerOptions)); - var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, forceExecuteAllAnalyzers: false, + var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); var analyzerResult = result.AnalysisResult[compilationWithAnalyzers.Analyzers[0]]; @@ -258,8 +254,8 @@ private static async Task AnalyzeAsync(TestWorkspace w analyzerReference.GetAnalyzers(project.Language).Where(a => a.GetType() == analyzerType).ToImmutableArray(), new WorkspaceAnalyzerOptions(project.AnalyzerOptions, ideOptions)); - var result = await executor.AnalyzeProjectAsync(project, analyzerDriver, forceExecuteAllAnalyzers: true, logPerformanceInfo: false, - getTelemetryInfo: false, cancellationToken); + var result = await executor.AnalyzeProjectAsync( + project, analyzerDriver, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken); return result.AnalysisResult[analyzerDriver.Analyzers[0]]; } @@ -314,17 +310,5 @@ public override void Initialize(AnalysisContext context) }); } } - - private class MyUpdateSource : AbstractHostDiagnosticUpdateSource - { - private readonly Workspace _workspace; - - public MyUpdateSource(Workspace workspace) - { - _workspace = workspace; - } - - public override Workspace Workspace => _workspace; - } } } diff --git a/src/VisualStudio/Core/Test.Next/TestUtils.cs b/src/VisualStudio/Core/Test.Next/TestUtils.cs index 1dec3254e957c..43141d811634a 100644 --- a/src/VisualStudio/Core/Test.Next/TestUtils.cs +++ b/src/VisualStudio/Core/Test.Next/TestUtils.cs @@ -2,35 +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. -#nullable disable - using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Serialization; using Xunit; -namespace Roslyn.VisualStudio.Next.UnitTests +namespace Roslyn.VisualStudio.Next.UnitTests; + +internal static class TestUtils { - internal static class TestUtils + public static void VerifyAssetStorage(IEnumerable> items, SolutionAssetCache storage) { - public static void VerifyAssetStorage(IEnumerable> items, SolutionAssetCache storage) + foreach (var kv in items) { - foreach (var kv in items) + if (kv.Value is ChecksumCollection) { - if (kv.Value is ChecksumCollection) - { - // ChecksumCollection itself won't be in asset storage. since - // it will be never asked from OOP side to host to sync. - // the collection is already part of - // Solution/Project/DocumentStateCheckum so syncing - // state checksum automatically bring in the collection. - // it only exist to calculate hierarchical checksum - continue; - } - - Assert.True(storage.TryGetAsset(kv.Key, out object _)); + // ChecksumCollection itself won't be in asset storage. since it will be never asked from OOP side + // to host to sync. the collection is already part of Solution/Project/DocumentStateChecksum so + // syncing state checksum automatically bring in the collection. it only exist to calculate + // hierarchical checksum + continue; } + + Assert.True(storage.TryGetAsset(kv.Key, out object? _)); } } } diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 3c03a208a23a3..edbf03f93fae6 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -25,9 +25,7 @@ Imports Roslyn.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics <[UseExportProvider]> Public Class ExternalDiagnosticUpdateSourceTests - Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ - .AddParts(GetType(MockDiagnosticUpdateSourceRegistrationService)) + Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures Public Sub TestExternalDiagnostics_SupportGetDiagnostics() @@ -40,39 +38,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics End Using End Sub - - Public Async Function TestExternalDiagnostics_RaiseEvents() As Task - Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty) - Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - Dim waiter = New AsynchronousOperationListener() - Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions) - Using source = New ExternalErrorDiagnosticUpdateSource( - workspace, service, workspace.GetService(Of IGlobalOperationNotificationService), waiter, CancellationToken.None) - - Dim project = workspace.CurrentSolution.Projects.First() - Dim diagnostic = GetDiagnosticData(project.Id) - - Dim expected = 1 - AddHandler source.DiagnosticsUpdated, Sub(o, argsCollection) - Dim args = argsCollection.Single() - Dim diagnostics = args.Diagnostics - Assert.Equal(expected, diagnostics.Length) - If expected = 1 Then - Assert.Equal(diagnostics(0), diagnostic) - End If - End Sub - - source.AddNewErrors(project.DocumentIds.First(), diagnostic) - source.OnSolutionBuildCompleted() - Await waiter.ExpeditedWaitAsync() - - expected = 0 - source.ClearErrors(project.Id) - Await waiter.ExpeditedWaitAsync() - End Using - End Using - End Function - Public Sub TestExternalDiagnostics_SupportedId() Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty) @@ -132,19 +97,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics source.AddNewErrors(project.Id, New HashSet(Of DiagnosticData)(SpecializedCollections.SingletonEnumerable(diagnostic)), map) - AddHandler source.DiagnosticsUpdated, Sub(o, argsCollection) - Dim args = argsCollection.Single() - Dim diagnostics = args.Diagnostics - Assert.Equal(1, diagnostics.Length) - End Sub - source.OnSolutionBuildCompleted() Await waiter.ExpeditedWaitAsync() Dim buildOnlyDiagnosticService = workspace.Services.GetRequiredService(Of IBuildOnlyDiagnosticsService) - Assert.Empty(buildOnlyDiagnosticService.GetBuildOnlyDiagnostics(project.DocumentIds.First())) - Assert.Empty(buildOnlyDiagnosticService.GetBuildOnlyDiagnostics(project.Id)) + Assert.Empty(Await buildOnlyDiagnosticService.GetBuildOnlyDiagnosticsAsync(project.DocumentIds.First(), CancellationToken.None)) + + Dim diagnostics = source.GetBuildErrors() + Assert.Equal(2, diagnostics.Length) End Using End Using End Function @@ -161,12 +122,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Using source = New ExternalErrorDiagnosticUpdateSource( workspace, service, workspace.GetService(Of IGlobalOperationNotificationService), waiter, CancellationToken.None) - AddHandler source.BuildProgressChanged, Sub(o, progress) - If progress = ExternalErrorDiagnosticUpdateSource.BuildProgress.Done Then - Assert.Equal(2, source.GetBuildErrors().Length) - End If - End Sub - Dim map = New Dictionary(Of DocumentId, HashSet(Of DiagnosticData))() map.Add(project.DocumentIds.First(), New HashSet(Of DiagnosticData)( SpecializedCollections.SingletonEnumerable(GetDiagnosticData(project.Id)))) @@ -176,6 +131,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics source.OnSolutionBuildCompleted() Await waiter.ExpeditedWaitAsync() + Assert.Equal(2, source.GetBuildErrors().Length) End Using End Using End Function @@ -255,69 +211,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics source.AddNewErrors(project.Id, diagnostic) source.AddNewErrors(project.Id, diagnostic) - AddHandler source.DiagnosticsUpdated, Sub(o, argsCollection) - Dim args = argsCollection.Single() - Dim diagnostics = args.Diagnostics - Assert.Equal(1, diagnostics.Length) - End Sub - - source.OnSolutionBuildCompleted() - - Await waiter.ExpeditedWaitAsync() - End Using - End Using - End Function - - - - Public Async Function TestExternalDiagnostics_CompilationEndAnalyzer(hasCompilationEndTag As Boolean) As Task - Using workspace = TestWorkspace.CreateCSharp(String.Empty, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) - Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - Dim analyzer = New CompilationEndAnalyzer(hasCompilationEndTag) - Dim compiler = DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp) - - Dim analyzerReference = New AnalyzerImageReference(New DiagnosticAnalyzer() {compiler, analyzer}.ToImmutableArray()) - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) - - Dim waiter = New AsynchronousOperationListener() - - Dim project = workspace.CurrentSolution.Projects.First() - - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) - Dim service = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim registration = service.CreateIncrementalAnalyzer(workspace) - - Using source = New ExternalErrorDiagnosticUpdateSource( - workspace, service, workspace.GetService(Of IGlobalOperationNotificationService), waiter, CancellationToken.None) - - Dim diagnostic = GetDiagnosticData(project.Id, id:=analyzer.SupportedDiagnostics(0).Id) - source.AddNewErrors(project.Id, diagnostic) - - Dim buildDiagnosticCallbackSeen = False - AddHandler source.DiagnosticsUpdated, Sub(o, argsCollection) - Dim args = argsCollection.Single() - buildDiagnosticCallbackSeen = True - - Dim diagnostics = args.Diagnostics - Assert.Equal(1, diagnostics.Length) - Assert.Equal(diagnostics(0).Properties(WellKnownDiagnosticPropertyNames.Origin), WellKnownDiagnosticTags.Build) - End Sub - source.OnSolutionBuildCompleted() Await waiter.ExpeditedWaitAsync() - - Assert.Equal(hasCompilationEndTag, buildDiagnosticCallbackSeen) - - Dim buildOnlyDiagnosticService = workspace.Services.GetRequiredService(Of IBuildOnlyDiagnosticsService) - Dim buildOnlyDiagnostics = buildOnlyDiagnosticService.GetBuildOnlyDiagnostics(project.Id) - If (hasCompilationEndTag) Then - Assert.Equal(1, buildOnlyDiagnostics.Length) - Assert.Equal(buildOnlyDiagnostics(0).Properties(WellKnownDiagnosticPropertyNames.Origin), WellKnownDiagnosticTags.Build) - Else - Assert.Empty(buildOnlyDiagnostics) - End If - + Dim diagnostics = source.GetBuildErrors() + Assert.Equal(1, diagnostics.Length) End Using End Using End Function @@ -336,7 +234,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim project = workspace.CurrentSolution.Projects.First() - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim service = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim registration = service.CreateIncrementalAnalyzer(workspace) @@ -346,17 +243,14 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim diagnostic = GetDiagnosticData(project.Id, id:=analyzer.SupportedDiagnostics(0).Id) source.AddNewErrors(project.Id, diagnostic) - AddHandler source.DiagnosticsUpdated, Sub(o, argsCollection) - Dim args = argsCollection.Single() - Dim diagnostics = args.Diagnostics - - Assert.Equal(1, diagnostics.Length) - Assert.Equal(diagnostics(0).Properties(WellKnownDiagnosticPropertyNames.Origin), WellKnownDiagnosticTags.Build) - End Sub - source.OnSolutionBuildCompleted() Await waiter.ExpeditedWaitAsync() + + Dim diagnostics = source.GetBuildErrors() + + Assert.Equal(1, diagnostics.Length) + Assert.Equal(diagnostics(0).Properties(WellKnownDiagnosticPropertyNames.Origin), WellKnownDiagnosticTags.Build) End Using End Using End Function @@ -376,7 +270,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim waiter = New AsynchronousOperationListener() Dim project = workspace.CurrentSolution.Projects.First() - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim service = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim registration = service.CreateIncrementalAnalyzer(workspace) @@ -386,53 +279,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim diagnostic = GetDiagnosticData(project.Id, id:=analyzer.SupportedDiagnostics(0).Id) source.AddNewErrors(project.Id, diagnostic) - Dim called = False - AddHandler source.DiagnosticsUpdated, Sub(o, a) - called = True - End Sub - source.OnSolutionBuildCompleted() Await waiter.ExpeditedWaitAsync() - - ' error is considered live error, so event shouldn't be raised - Assert.False(called) - End Using - End Using - End Function - - - Public Async Function TestBuildProgressUpdated() As Task - Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty) - Dim waiter = New AsynchronousOperationListener() - - workspace.AddTestProject(New EditorTestHostProject(workspace, language:=LanguageNames.CSharp)) - - Dim projectId1 = workspace.CurrentSolution.ProjectIds(0) - Dim projectId2 = workspace.CurrentSolution.ProjectIds(1) - - Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions) - Using source = New ExternalErrorDiagnosticUpdateSource( - workspace, service, workspace.GetService(Of IGlobalOperationNotificationService), waiter, CancellationToken.None) - - source.AddNewErrors(projectId1, GetDiagnosticData(projectId1)) - Await waiter.ExpeditedWaitAsync() - - Dim numberOfUpdateCalls = 0 - AddHandler source.BuildProgressChanged, Sub(o, progress) - If progress = ExternalErrorDiagnosticUpdateSource.BuildProgress.Updated Then - numberOfUpdateCalls += 1 - Assert.Equal(numberOfUpdateCalls, source.GetBuildErrors().Length) - ElseIf progress = ExternalErrorDiagnosticUpdateSource.BuildProgress.Done Then - Assert.Equal(2, source.GetBuildErrors().Length) - End If - End Sub - - source.AddNewErrors(projectId2, GetDiagnosticData(projectId2)) - Await waiter.ExpeditedWaitAsync() - - source.OnSolutionBuildCompleted() - Await waiter.ExpeditedWaitAsync() + Dim diagnostics = source.GetBuildErrors() + Assert.NotEmpty(diagnostics) End Using End Using End Function @@ -452,7 +303,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim project = workspace.CurrentSolution.Projects.First() - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim service = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) Dim registration = service.CreateIncrementalAnalyzer(workspace) Using source = New ExternalErrorDiagnosticUpdateSource( @@ -472,13 +322,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics location:=New DiagnosticDataLocation(New FileLinePositionSpan("Test.txt", New LinePosition(4, 4), New LinePosition(4, 4)), documentId:=Nothing), language:=project.Language) - AddHandler service.DiagnosticsUpdated, Sub(o, argsCollection) - Dim args = argsCollection.Single() - Dim diagnostics = args.Diagnostics + 'AddHandler service.DiagnosticsUpdated, Sub(o, argsCollection) + ' Dim args = argsCollection.Single() + ' Dim diagnostics = args.Diagnostics - Assert.Single(diagnostics) - Assert.Equal(diagnostics(0).Id, diagnostic.Id) - End Sub + ' Assert.Single(diagnostics) + ' Assert.Equal(diagnostics(0).Id, diagnostic.Id) + ' End Sub source.AddNewErrors(project.Id, diagnostic) Await waiter.ExpeditedWaitAsync() @@ -492,91 +342,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics End Using End Function - - Public Async Function TestExternalDiagnostics_BuildOnlyClearedOnDocumentChanged() As Task - Using workspace = TestWorkspace.CreateCSharp("class C { }", composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) - Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - Dim analyzer = New CompilationEndAnalyzer(hasCompilationEndTag:=True) - Dim compiler = DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp) - - Dim analyzerReference = New AnalyzerImageReference(New DiagnosticAnalyzer() {compiler, analyzer}.ToImmutableArray()) - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) - - Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider)() - Dim waiter = TryCast(listenerProvider.GetListener(FeatureAttribute.ErrorList), AsynchronousOperationListener) - - Dim project = workspace.CurrentSolution.Projects.First() - Dim document = project.Documents.Single() - - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) - Dim service = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) - Dim registration = service.CreateIncrementalAnalyzer(workspace) - Using source = New ExternalErrorDiagnosticUpdateSource( - workspace, service, workspace.GetService(Of IGlobalOperationNotificationService), waiter, CancellationToken.None) - - Dim diagnostic = New DiagnosticData( - id:=analyzer.Descriptor.Id, - category:=analyzer.Descriptor.Category, - message:=analyzer.Descriptor.MessageFormat.ToString(), - severity:=analyzer.Descriptor.DefaultSeverity, - defaultSeverity:=analyzer.Descriptor.DefaultSeverity, - isEnabledByDefault:=analyzer.Descriptor.IsEnabledByDefault, - warningLevel:=0, - customTags:=analyzer.Descriptor.CustomTags.AsImmutable(), - properties:=DiagnosticData.PropertiesForBuildDiagnostic, - project.Id, - location:=New DiagnosticDataLocation( - New FileLinePositionSpan(document.FilePath, New LinePositionSpan()), document.Id), - language:=project.Language) - - Dim actualDiagnostic As DiagnosticData = Nothing - Dim diagnosticAdded = False - Dim diagnosticRemoved = False - AddHandler source.DiagnosticsUpdated, Sub(o, argsCollection) - Dim args = argsCollection.Single() - Assert.Equal(document.Id, args.DocumentId) - Dim diagnostics = args.Diagnostics - If args.Kind = DiagnosticsUpdatedKind.DiagnosticsCreated Then - actualDiagnostic = Assert.Single(diagnostics) - diagnosticAdded = True - Else - Assert.Equal(DiagnosticsUpdatedKind.DiagnosticsRemoved, args.Kind) - Assert.Empty(diagnostics) - actualDiagnostic = Nothing - diagnosticRemoved = True - End If - End Sub - - source.AddNewErrors(document.Id, diagnostic) - Await waiter.ExpeditedWaitAsync() - - source.OnSolutionBuildCompleted() - Await waiter.ExpeditedWaitAsync() - - Dim diagnosticServiceWaiter = TryCast(listenerProvider.GetListener(FeatureAttribute.DiagnosticService), AsynchronousOperationListener) - Await diagnosticServiceWaiter.ExpeditedWaitAsync() - - Assert.True(diagnosticAdded) - Assert.NotNull(actualDiagnostic) - Assert.Equal(actualDiagnostic, diagnostic) - - Dim buildOnlyDiagnosticService = workspace.Services.GetRequiredService(Of IBuildOnlyDiagnosticsService) - Dim buildOnlyDiagnostic = Assert.Single(buildOnlyDiagnosticService.GetBuildOnlyDiagnostics(document.Id)) - Assert.Equal(buildOnlyDiagnostic, diagnostic) - - ' Verify build-only diagnostics cleared after document changed event - document = document.WithText(SourceText.From("class C2 { }")) - source.GetTestAccessor().OnWorkspaceChanged(workspace, New WorkspaceChangeEventArgs(WorkspaceChangeKind.DocumentChanged, - oldSolution:=workspace.CurrentSolution, newSolution:=document.Project.Solution, project.Id, document.Id)) - Await waiter.ExpeditedWaitAsync() - - Assert.True(diagnosticRemoved) - Assert.Null(actualDiagnostic) - Assert.Empty(buildOnlyDiagnosticService.GetBuildOnlyDiagnostics(document.Id)) - End Using - End Using - End Function - Private Class CompilationEndAnalyzer Inherits DiagnosticAnalyzer @@ -637,7 +402,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics End Function Private Class TestDiagnosticAnalyzerService - Implements IDiagnosticAnalyzerService, IDiagnosticUpdateSource + Implements IDiagnosticAnalyzerService Private ReadOnly _analyzerInfoCache As DiagnosticAnalyzerInfoCache @@ -654,10 +419,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics End Get End Property - Public Event DiagnosticsUpdated As EventHandler(Of ImmutableArray(Of DiagnosticsUpdatedArgs)) Implements IDiagnosticUpdateSource.DiagnosticsUpdated - Public Event DiagnosticsCleared As EventHandler Implements IDiagnosticUpdateSource.DiagnosticsCleared - - Public Sub Reanalyze(workspace As Workspace, projectIds As IEnumerable(Of ProjectId), documentIds As IEnumerable(Of DocumentId), highPriority As Boolean) Implements IDiagnosticAnalyzerService.Reanalyze + Public Sub RequestDiagnosticRefresh() Implements IDiagnosticAnalyzerService.RequestDiagnosticRefresh End Sub Public Function GetDiagnosticsForSpanAsync(document As TextDocument, range As TextSpan?, shouldIncludeDiagnostic As Func(Of String, Boolean), includeCompilerDiagnostics As Boolean, includeSuppressedDiagnostics As Boolean, priority As ICodeActionRequestPriorityProvider, addOperationScope As Func(Of String, IDisposable), diagnosticKinds As DiagnosticKind, isExplicit As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync @@ -668,11 +430,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function - Public Function GetDiagnosticsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, includeSuppressedDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsAsync - Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() - End Function - - Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync + Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), getDocuments As Func(Of Project, DocumentId, IReadOnlyList(Of DocumentId)), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function diff --git a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index 7e8b130d74ac7..15fc95f510bd7 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -62,4 +62,8 @@ + + + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb index e37a598ab234b..aa0f91e6726dd 100644 --- a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb @@ -4,7 +4,6 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeStyle -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Options.EditorConfig Imports Microsoft.CodeAnalysis.Test.Utilities @@ -124,6 +123,7 @@ csharp_style_prefer_switch_expression = true csharp_style_conditional_delegate_call = true # Modifier preferences +csharp_prefer_static_anonymous_function = true csharp_prefer_static_local_function = true csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async csharp_style_prefer_readonly_struct = true @@ -371,6 +371,7 @@ csharp_style_prefer_switch_expression = true csharp_style_conditional_delegate_call = true # Modifier preferences +csharp_prefer_static_anonymous_function = true csharp_prefer_static_local_function = true csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async csharp_style_prefer_readonly_struct = true diff --git a/src/VisualStudio/Core/Test/Preview/PreviewChangesTests.vb b/src/VisualStudio/Core/Test/Preview/PreviewChangesTests.vb index 023b3d82db9ab..70e7e8f3d0518 100644 --- a/src/VisualStudio/Core/Test/Preview/PreviewChangesTests.vb +++ b/src/VisualStudio/Core/Test/Preview/PreviewChangesTests.vb @@ -45,7 +45,6 @@ Class C Dim componentModel = New MockComponentModel(workspace.ExportProvider) Dim previewEngine = New PreviewEngine( - workspace.ExportProvider.GetExportedValue(Of IThreadingContext), "Title", "helpString", "description", "topLevelItemName", Glyph.Assembly, forkedDocument.Project.Solution, workspace.CurrentSolution, @@ -106,7 +105,6 @@ Class C Dim componentModel = New MockComponentModel(workspace.ExportProvider) Dim previewEngine = New PreviewEngine( - workspace.ExportProvider.GetExportedValue(Of IThreadingContext), "Title", "helpString", "description", "topLevelItemName", Glyph.Assembly, newSolution, workspace.CurrentSolution, @@ -142,7 +140,6 @@ Class C Dim componentModel = New MockComponentModel(workspace.ExportProvider) Dim previewEngine = New PreviewEngine( - workspace.ExportProvider.GetExportedValue(Of IThreadingContext), "Title", "helpString", "description", "topLevelItemName", Glyph.Assembly, forkedDocument.Project.Solution, workspace.CurrentSolution, @@ -208,7 +205,6 @@ Class C newSolution = newSolution.AddDocument(addedDocumentId2, "test5.cs", "// This file will be unchecked and not added!") Dim previewEngine = New PreviewEngine( - workspace.ExportProvider.GetExportedValue(Of IThreadingContext), "Title", "helpString", "description", "topLevelItemName", Glyph.Assembly, newSolution, workspace.CurrentSolution, @@ -289,7 +285,6 @@ End Class Dim componentModel = New MockComponentModel(workspace.ExportProvider) Dim previewEngine = New PreviewEngine( - workspace.ExportProvider.GetExportedValue(Of IThreadingContext), "Title", "helpString", "description", "topLevelItemName", Glyph.Assembly, updatedSolution, workspace.CurrentSolution, diff --git a/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb b/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb index c24613ccf8372..98c8bcef7d1f1 100644 --- a/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb +++ b/src/VisualStudio/Core/Test/Progression/GraphProviderTests.vb @@ -15,14 +15,14 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression Public Sub TestGetContainsGraphQueries() Dim context = CreateGraphContext(GraphContextDirection.Contains, Array.Empty(Of GraphCategory)()) - Dim queries = RoslynGraphProvider.GetGraphQueries(context, asyncListener:=Nothing) + Dim queries = RoslynGraphProvider.GetGraphQueries(context) Assert.Equal(queries.Single().GetType(), GetType(ContainsGraphQuery)) End Sub Public Sub TestGetContainsGraphQueriesWithTarget() Dim context = CreateGraphContext(GraphContextDirection.Target, {CodeLinkCategories.Contains}) - Dim queries = RoslynGraphProvider.GetGraphQueries(context, asyncListener:=Nothing) + Dim queries = RoslynGraphProvider.GetGraphQueries(context) Assert.Equal(queries.Single().GetType(), GetType(ContainsGraphQuery)) End Sub diff --git a/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests_NavigateTo.vb b/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests_NavigateTo.vb index 2de0176ada9ec..7b2bca02f1228 100644 --- a/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests_NavigateTo.vb +++ b/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests_NavigateTo.vb @@ -27,7 +27,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("C", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("C", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -59,7 +59,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("F", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("F", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -91,7 +91,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("M", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("M", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -136,7 +136,7 @@ End Namespace ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("C", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("C", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -185,7 +185,7 @@ End Namespace ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("Goo", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("Goo", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -230,7 +230,7 @@ End Namespace ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("Z", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("Z", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -266,7 +266,7 @@ End Namespace ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("D.B", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("D.B", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -298,7 +298,7 @@ End Namespace ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("C.B", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("C.B", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -321,7 +321,7 @@ End Namespace ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("D.B", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("D.B", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -353,7 +353,7 @@ End Namespace ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("A.D.B", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("A.D.B", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) AssertSimplifiedGraphIs( outputContext.Graph, @@ -385,7 +385,7 @@ End Namespace ) Dim outputContext = Await testState.GetGraphContextAfterQuery( - New Graph(), New SearchGraphQuery("A.D.B", NavigateToSearchScope.AllDocuments, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) + New Graph(), New SearchGraphQuery("A.D.B", NavigateToDocumentSupport.AllDocuments), GraphContextDirection.Custom) ' When searching, don't descend into projects with a null FilePath because they are artifacts and not ' representable in the Solution Explorer, e.g., Venus projects create sub-projects with a null file diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb index d22fe45fbdd0d..8e4c35d039482 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb @@ -21,22 +21,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Public Class VisualStudioAnalyzerTests - Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ - .AddParts(GetType(MockDiagnosticUpdateSourceRegistrationService)) + Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures Public Sub GetReferenceCalledMultipleTimes() Using workspace = New TestWorkspace(composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) - Dim lazyWorkspace = New Lazy(Of VisualStudioWorkspace)( - Function() - Return Nothing - End Function) - - Dim registrationService = Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) - Dim hostDiagnosticUpdateSource = New HostDiagnosticUpdateSource(lazyWorkspace, registrationService) - - Using tempRoot = New TempRoot(), analyzer = New ProjectAnalyzerReference(tempRoot.CreateFile().Path, hostDiagnosticUpdateSource, ProjectId.CreateNewId(), LanguageNames.VisualBasic) + Using tempRoot = New TempRoot(), analyzer = New ProjectAnalyzerReference(tempRoot.CreateFile().Path, HostDiagnosticUpdateSource.Instance, ProjectId.CreateNewId(), LanguageNames.VisualBasic) Dim reference1 = analyzer.GetReference() Dim reference2 = analyzer.GetReference() @@ -45,34 +35,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim End Using End Sub - - Public Sub AnalyzerErrorsAreUpdated() - Using workspace = New TestWorkspace(composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) - Dim lazyWorkspace = New Lazy(Of VisualStudioWorkspace)( - Function() - Return Nothing - End Function) - - Dim file = Path.GetTempFileName() - - Dim registrationService = Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) - Dim hostDiagnosticUpdateSource = New HostDiagnosticUpdateSource(lazyWorkspace, registrationService) - - Dim eventHandler = New EventHandlers(file) - AddHandler hostDiagnosticUpdateSource.DiagnosticsUpdated, AddressOf eventHandler.DiagnosticAddedTest - - Using analyzer = New ProjectAnalyzerReference(file, hostDiagnosticUpdateSource, ProjectId.CreateNewId(), LanguageNames.VisualBasic) - Dim reference = analyzer.GetReference() - reference.GetAnalyzers(LanguageNames.VisualBasic) - - RemoveHandler hostDiagnosticUpdateSource.DiagnosticsUpdated, AddressOf eventHandler.DiagnosticAddedTest - AddHandler hostDiagnosticUpdateSource.DiagnosticsUpdated, AddressOf EventHandlers.DiagnosticRemovedTest - End Using - - IO.File.Delete(file) - End Using - End Sub - Private Class EventHandlers Public File As String diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb new file mode 100644 index 0000000000000..7f42c3704baf2 --- /dev/null +++ b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb @@ -0,0 +1,97 @@ +' Licensed to the .NET Foundation under one or more 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 System.Reflection +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Completion +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.VisualStudio.LanguageServices +Imports Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings +Imports Newtonsoft.Json.Linq + +Namespace Roslyn.VisualStudio.VisualBasic.UnitTests.UnifiedSettings + Public Class VisualBasicUnifiedSettingsTests + Inherits UnifiedSettingsTests + + Friend Overrides ReadOnly Property OnboardedOptions As ImmutableArray(Of IOption2) + Get + Return ImmutableArray.Create(Of IOption2)( + CompletionOptionsStorage.TriggerOnTypingLetters, + CompletionOptionsStorage.TriggerOnDeletion, + CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems, + CompletionViewOptionsStorage.ShowCompletionItemFilters, + CompletionOptionsStorage.SnippetsBehavior, + CompletionOptionsStorage.EnterKeyBehavior, + CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, + CompletionViewOptionsStorage.EnableArgumentCompletionSnippets + ) + End Get + End Property + + Friend Overrides Function GetEnumOptionValues([option] As IOption2) As Object() + Dim allValues = [Enum].GetValues([option].Type).Cast(Of Object) + If [option].Equals(CompletionOptionsStorage.SnippetsBehavior) Then + 'SnippetsRule.Default is used as a stub value, overridden per language at runtime. + ' It is not shown in the option page + Return allValues.Where(Function(value) Not value.Equals(SnippetsRule.Default)).ToArray() + ElseIf [option].Equals(CompletionOptionsStorage.EnterKeyBehavior) Then + ' EnterKeyRule.Default is used as a stub value, overridden per language at runtime. + ' It Is Not shown in the option page + Return allValues.Where(Function(value) Not value.Equals(EnterKeyRule.Default)).ToArray() + End If + + Return MyBase.GetEnumOptionValues([option]) + End Function + + Friend Overrides Function GetOptionsDefaultValue([option] As IOption2) As Object + ' The default values of some options are set at runtime. option.defaultValue is just a dummy value in this case. + ' However, in unified settings we always set the correct value in registration.json. + If [option].Equals(CompletionOptionsStorage.SnippetsBehavior) Then + ' CompletionOptionsStorage.SnippetsBehavior's default value is SnippetsRule.Default. + ' It's overridden differently per-language at runtime. + Return SnippetsRule.IncludeAfterTypingIdentifierQuestionTab + ElseIf [option].Equals(CompletionOptionsStorage.EnterKeyBehavior) Then + ' CompletionOptionsStorage.EnterKeyBehavior's default value is EnterKeyBehavior.Default. + ' It's overridden differently per-language at runtime. + Return EnterKeyRule.Always + ElseIf [option].Equals(CompletionOptionsStorage.TriggerOnDeletion) Then + ' CompletionOptionsStorage.TriggerOnDeletion's default value is null. + ' It's enabled by default for Visual Basic + Return True + ElseIf [option].Equals(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces) Then + ' CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces's default value is null + ' It's enabled by default for Visual Basic + Return True + ElseIf [option].Equals(CompletionViewOptionsStorage.EnableArgumentCompletionSnippets) Then + ' CompletionViewOptionsStorage.EnableArgumentCompletionSnippets' default value is null + ' It's disabled by default for Visual Basic + Return False + End If + + Return MyBase.GetOptionsDefaultValue([option]) + End Function + + + Public Async Function IntelliSensePageTests() As Task + Using registrationFileStream = GetType(VisualBasicUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("visualBasicSettings.registration.json") + Using pkgDefFileStream = GetType(VisualBasicUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("PackageRegistration.pkgdef") + Using pkgDefFileReader = New StreamReader(pkgDefFileStream) + Using reader = New StreamReader(registrationFileStream) + Dim registrationFile = Await reader.ReadToEndAsync().ConfigureAwait(False) + Dim pkgDefFile = Await pkgDefFileReader.ReadToEndAsync().ConfigureAwait(False) + Dim registrationJsonObject = JObject.Parse(registrationFile, New JsonLoadSettings()) + Dim categoriesTitle = registrationJsonObject.SelectToken("$.categories['textEditor.basic'].title") + Assert.Equal("Visual Basic", categoriesTitle) + Dim optionPageId = registrationJsonObject.SelectToken("$.categories['textEditor.basic.intellisense'].legacyOptionPageId") + Assert.Equal(Guids.VisualBasicOptionPageIntelliSenseIdString, optionPageId.ToString()) + TestUnifiedSettingsCategory(registrationJsonObject, categoryBasePath:="textEditor.basic.intellisense", languageName:=LanguageNames.VisualBasic, pkgDefFile) + End Using + End Using + End Using + End Using + End Function + End Class +End Namespace diff --git a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb index 39fa9362956d1..25ca0b50529f7 100644 --- a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb +++ b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb @@ -38,9 +38,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Venus Public Class DocumentService_IntegrationTests - Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ - .AddParts(GetType(MockDiagnosticUpdateSourceRegistrationService)) + Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures Public Async Function TestFindUsageIntegration() As System.Threading.Tasks.Task @@ -231,7 +229,6 @@ class { } ' confirm there are errors Assert.True(model.GetDiagnostics().Any()) - Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)()) ' confirm diagnostic support is off for the document @@ -241,9 +238,9 @@ class { } diagnosticService.CreateIncrementalAnalyzer(workspace) ' confirm that IDE doesn't report the diagnostics - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, - includeSuppressedDiagnostics:=False, includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.False(diagnostics.Any()) End Using End Function diff --git a/src/VisualStudio/DevKit/Impl/EditAndContinue/ManagedHotReloadLanguageServiceBridge.cs b/src/VisualStudio/DevKit/Impl/EditAndContinue/ManagedHotReloadLanguageServiceBridge.cs index 96571de4a4b48..e9cb2154246b9 100644 --- a/src/VisualStudio/DevKit/Impl/EditAndContinue/ManagedHotReloadLanguageServiceBridge.cs +++ b/src/VisualStudio/DevKit/Impl/EditAndContinue/ManagedHotReloadLanguageServiceBridge.cs @@ -4,10 +4,8 @@ using System; using System.ComponentModel.Composition; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.BrokeredServices; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Debugger.Contracts.HotReload; @@ -16,22 +14,13 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; -[ExportBrokeredService(MonikerName, ServiceVersion, Audience = ServiceAudience.Local)] +[ExportBrokeredService(ManagedHotReloadLanguageServiceDescriptor.MonikerName, ManagedHotReloadLanguageServiceDescriptor.ServiceVersion, Audience = ServiceAudience.Local)] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed partial class ManagedHotReloadLanguageServiceBridge(InternalContracts.IManagedHotReloadLanguageService service) : IManagedHotReloadLanguageService, IExportedBrokeredService { - private const string ServiceName = "ManagedHotReloadLanguageService"; - private const string ServiceVersion = "0.1"; - private const string MonikerName = BrokeredServiceDescriptors.LanguageServerComponentNamespace + "." + BrokeredServiceDescriptors.LanguageServerComponentName + "." + ServiceName; - - public static readonly ServiceJsonRpcDescriptor ServiceDescriptor = BrokeredServiceDescriptors.CreateServerServiceDescriptor(ServiceName, new(ServiceVersion)); - - static ManagedHotReloadLanguageServiceBridge() - => Debug.Assert(ServiceDescriptor.Moniker.Name == MonikerName); - ServiceRpcDescriptor IExportedBrokeredService.Descriptor - => ServiceDescriptor; + => ManagedHotReloadLanguageServiceDescriptor.Descriptor; public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; diff --git a/src/VisualStudio/IntegrationTest/IntegrationTestBuildProject.csproj b/src/VisualStudio/IntegrationTest/IntegrationTestBuildProject.csproj index cdd4ca9bcbd6b..fd37008e6ce7e 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTestBuildProject.csproj +++ b/src/VisualStudio/IntegrationTest/IntegrationTestBuildProject.csproj @@ -13,7 +13,6 @@ - diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs index 8af34dd060cae..d3da6ef64f484 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs @@ -309,7 +309,7 @@ public async Task GetLightBulbPreviewClassificationsAsync( activeSession.Collapse(); var classifier = classifierAggregatorService.GetClassifier(preview); var classifiedSpans = classifier.GetClassificationSpans(new SnapshotSpan(preview.TextBuffer.CurrentSnapshot, 0, preview.TextBuffer.CurrentSnapshot.Length)); - return classifiedSpans.ToArray(); + return [.. classifiedSpans]; } activeSession.Collapse(); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs index 517c21d606ad1..c9ca70e510dc0 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs @@ -157,7 +157,7 @@ static ScreenshotInProcess() return; } - frames = s_frames.ToArray(); + frames = [.. s_frames]; } // Make sure the frames are processed in order of their timestamps @@ -303,7 +303,7 @@ private static (TimeSpan elapsed, BitmapSource image, Size offset)[] DetectChang Marshal.FreeHGlobal(imageBuffer); } - return resultFrames.ToArray(); + return [.. resultFrames]; } private static void WritePngSignature(Stream stream, byte[] buffer) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs index e108e60b020f1..60e015c0f5bbe 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs @@ -53,7 +53,6 @@ public async Task ResetGlobalOptionsAsync(CancellationToken cancellationToken) configurationService.Clear(); var globalOptions = await GetComponentModelServiceAsync(cancellationToken); - ResetOption(globalOptions, DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag); ResetOption(globalOptions, CSharpCodeStyleOptions.NamespaceDeclarations); ResetOption(globalOptions, InheritanceMarginOptionsStorage.InheritanceMarginCombinedWithIndicatorMargin); ResetOption(globalOptions, InlineRenameSessionOptionsStorage.PreviewChanges); @@ -64,6 +63,8 @@ public async Task ResetGlobalOptionsAsync(CancellationToken cancellationToken) ResetOption(globalOptions, InlineRenameUIOptionsStorage.UseInlineAdornment); ResetOption(globalOptions, MetadataAsSourceOptionsStorage.NavigateToDecompiledSources); ResetOption(globalOptions, WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspace); + ResetOption(globalOptions, WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution); + ResetOption(globalOptions, WorkspaceConfigurationOptionsStorage.SourceGeneratorExecutionBalancedFeatureFlag); ResetPerLanguageOption(globalOptions, BlockStructureOptionsStorage.CollapseSourceLinkEmbeddedDecompiledFilesWhenFirstOpened); ResetPerLanguageOption(globalOptions, CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces); ResetPerLanguageOption(globalOptions, CompletionOptionsStorage.TriggerInArgumentLists); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs index c11174300d08a..f520004406aed 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs @@ -22,7 +22,7 @@ public InfrastructureTests() protected override string LanguageName => LanguageNames.CSharp; - [IdeFact] + [IdeFact(Skip = "https://github.com/dotnet/roslyn/issues/73099")] public async Task CanCloseSaveDialog() { await SetUpEditorAsync( diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicClassification.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicClassification.cs index 73dd8249af105..0585788a411e6 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicClassification.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicClassification.cs @@ -43,7 +43,7 @@ End Class End Namespace", HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync("MathAlias", charsOffset: 0, HangMitigatingCancellationToken); - await TestServices.EditorVerifier.CurrentTokenTypeAsync(tokenType: "identifier", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.CurrentTokenTypeAsync(tokenType: "class name", HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync("Namespace", charsOffset: 0, HangMitigatingCancellationToken); await TestServices.EditorVerifier.CurrentTokenTypeAsync(tokenType: "keyword", HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync("summary", charsOffset: 0, HangMitigatingCancellationToken); diff --git a/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs b/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs index e922257ddd994..38c1fb7179dc0 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs @@ -23,7 +23,7 @@ public static string GetLanguageServerProviderServiceName(string[] contentTypes) public static string GetLanguageServerProviderServiceName(string lspServiceName) => LanguageServerProviderServiceName + "-" + lspServiceName; - public static string GetContentTypesName(string[] contentTypes) => string.Join("-", contentTypes.OrderBy(c => c).ToArray()); + public static string GetContentTypesName(string[] contentTypes) => string.Join("-", [.. contentTypes.OrderBy(c => c)]); public static bool IsContentTypeRemote(string contentType) => contentType.EndsWith("-remote"); diff --git a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs index ef96a72e3debb..b6eff7bf64e92 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs @@ -175,7 +175,7 @@ private async Task UpdatePathsToRemoteFilesAsync(CollaborationSession session) #pragma warning disable CS8602 // Dereference of a possibly null reference. (Can localRoot be null here?) var splitRoot = localRoot.TrimEnd('\\').Split('\\'); #pragma warning restore CS8602 // Dereference of a possibly null reference. - splitRoot[splitRoot.Length - 1] = "~external"; + splitRoot[^1] = "~external"; var externalPath = string.Join("\\", splitRoot) + "\\"; remoteRootPaths.Add(localRoot); diff --git a/src/VisualStudio/Setup.ServiceHub/Directory.Build.props b/src/VisualStudio/Setup.ServiceHub/Directory.Build.props index 97c5e74599af2..4dff2f71e7b9d 100644 --- a/src/VisualStudio/Setup.ServiceHub/Directory.Build.props +++ b/src/VisualStudio/Setup.ServiceHub/Directory.Build.props @@ -24,7 +24,6 @@ .\ - core diff --git a/src/VisualStudio/Setup/Directory.Build.targets b/src/VisualStudio/Setup/Directory.Build.targets index f3be81af53814..e70ab346f5b09 100644 --- a/src/VisualStudio/Setup/Directory.Build.targets +++ b/src/VisualStudio/Setup/Directory.Build.targets @@ -2,5 +2,4 @@ - \ No newline at end of file diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index 5183dad27cba1..d739438f4952f 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -19,12 +19,6 @@ Microsoft.CodeAnalysis.LanguageServices - - - - .\ - desktop @@ -141,12 +135,6 @@ true BindingRedirect - - Microsoft.CommonLanguageServerProtocolFramework - BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup - true - BindingRedirect - LiveShareLanguageServices BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup @@ -328,6 +316,19 @@ PublishProjectOutputGroup + + SemanticSearchRefs + + TargetFramework=$(NetRoslyn) + true + false + + false + SemanticSearchRefs + PublishProjectOutputGroup + + false + diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb index fd973ec00660e..8a25462f33b34 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb @@ -50,7 +50,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr ' GetType(MockWorkspaceEventListenerProvider)) Private Shared ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeaturesWpf _ - .AddExcludedPartTypes(GetType(IDiagnosticUpdateSourceRegistrationService)) _ .AddParts( GetType(FileChangeWatcherProvider), GetType(MockVisualStudioWorkspace), @@ -66,9 +65,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr GetType(VsMetadataServiceFactory), GetType(VisualStudioMetadataReferenceManagerFactory), GetType(MockWorkspaceEventListenerProvider), - GetType(HostDiagnosticUpdateSource), GetType(HierarchyItemToProjectIdMap), - GetType(DiagnosticService)) + GetType(DiagnosticAnalyzerService)) Private ReadOnly _workspace As VisualStudioWorkspaceImpl Private ReadOnly _projectFilePaths As New List(Of String) diff --git a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb new file mode 100644 index 0000000000000..ecf8ba700f9bf --- /dev/null +++ b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb @@ -0,0 +1,228 @@ +' Licensed to the .NET Foundation under one or more 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.Hashing +Imports System.Text +Imports System.Text.RegularExpressions +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.VisualStudio.LanguageServices.Options +Imports Microsoft.VisualStudio.LanguageServices.Options.VisualStudioOptionStorage +Imports Newtonsoft.Json.Linq +Imports Roslyn.Test.Utilities +Imports Roslyn.Utilities + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings + Partial Public MustInherit Class UnifiedSettingsTests + ' Onboarded options in Unified Settings registration file + Friend MustOverride ReadOnly Property OnboardedOptions As ImmutableArray(Of IOption2) + + ' Override this method to if the option use different default value. + Friend Overridable Function GetOptionsDefaultValue([option] As IOption2) As Object + Return [option].DefaultValue + End Function + + ' Override this method to specify all possible enum values in option page. + Friend Overridable Function GetEnumOptionValues([option] As IOption2) As Object() + Dim type = [option].Definition.Type + Assert.True(type.IsEnum) + Return [Enum].GetValues(type).Cast(Of Object).AsArray() + End Function + + Protected Sub TestUnifiedSettingsCategory(registrationJsonObject As JObject, categoryBasePath As String, languageName As String, pkdDefFile As String) + Dim actualAllSettings = registrationJsonObject.SelectToken($"$.properties").Children.OfType(Of JProperty). + Where(Function(setting) setting.Name.StartsWith(categoryBasePath)). + Select(Function(setting) setting.Name). + OrderBy(Function(name) name). + ToArray() + + Dim expectedAllSettings = OnboardedOptions.Select(Function(onboardedOption) s_unifiedSettingsStorage(onboardedOption.Definition.ConfigName).GetUnifiedSettingsPath(languageName)). + OrderBy(Function(name) name). + ToArray() + Assert.Equal(expectedAllSettings, actualAllSettings) + + For Each onboardedOption In OnboardedOptions + Dim optionName = onboardedOption.Definition.ConfigName + Dim settingStorage As UnifiedSettingsStorage = Nothing + If s_unifiedSettingsStorage.TryGetValue(optionName, settingStorage) Then + Dim unifiedSettingsPath = settingStorage.GetUnifiedSettingsPath(languageName) + VerifyType(registrationJsonObject, unifiedSettingsPath, onboardedOption) + + Dim expectedDefaultValue = GetOptionsDefaultValue(onboardedOption) + Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingsPath}').default") + Assert.Equal(expectedDefaultValue.ToString().ToCamelCase(), actualDefaultValue.ToString().ToCamelCase()) + + If onboardedOption.Type.IsEnum Then + ' Enum settings contains special setup. + VerifyEnum(registrationJsonObject, unifiedSettingsPath, onboardedOption, languageName) + Else + VerifySettings(registrationJsonObject, unifiedSettingsPath, onboardedOption, languageName) + End If + Else + ' Can't find the option in the storage dictionary + Throw ExceptionUtilities.UnexpectedValue(optionName) + End If + Next + + Dim registrationFileBytes = ASCIIEncoding.ASCII.GetBytes(registrationJsonObject.ToString()) + Dim hash = XxHash128.Hash(registrationFileBytes) + Dim tagBytes = hash.Take(8).ToArray() + Dim expectedCacheTagValue = BitConverter.ToInt64(tagBytes, 0).ToString("X16") + + Dim regexExp = New Regex("""CacheTag""=qword:\w{16}") + Dim match = regexExp.Match(pkdDefFile, 0).Value + Dim actual = match.Substring(match.Length - 16) + ' Please change the CacheTag value in pkddef if you modify the unified settings regirstration file + Assert.Equal(expectedCacheTagValue, actual) + End Sub + + Private Shared Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) + VerifyMigration(registrationJsonObject, unifiedSettingPath, [option], languageName) + End Sub + + Private Sub VerifyEnum(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) + Dim actualEnumValues = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').enum").Select(Function(token) token.ToString()).OrderBy(Function(value) value) + Dim expectedEnumValues = GetEnumOptionValues([option]).Select(Function(value) value.ToString().ToCamelCase()).OrderBy(Function(value) value) + AssertEx.Equal(expectedEnumValues, actualEnumValues) + VerifyEnumMigration(registrationJsonObject, unifiedSettingPath, [option], languageName) + End Sub + + Private Shared Sub VerifyType(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + Dim actualType = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].type") + Dim expectedType = [option].Definition.Type + If expectedType.IsEnum Then + ' Enum is string in json + Assert.Equal("string", actualType.ToString()) + Else + Dim expectedTypeName = ConvertTypeNameToJsonType([option].Definition.Type) + Assert.Equal(expectedTypeName, actualType.ToString()) + End If + End Sub + + Private Shared Function ConvertTypeNameToJsonType(optionType As Type) As String + Dim underlyingType = Nullable.GetUnderlyingType(optionType) + ' If the type is Nullable type, its mapping type in unified setting page would be the normal type + ' These options would need to change to non-nullable form + ' See https://github.com/dotnet/roslyn/issues/69367 + If underlyingType Is Nothing Then + Return optionType.Name.ToCamelCase() + Else + Return underlyingType.Name.ToCamelCase() + End If + End Function + + Private Sub VerifyEnumMigration(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) + Dim actualMigration = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').migration") + Dim migrationProperty = DirectCast(actualMigration.Children().Single(), JProperty) + Dim migrationType = migrationProperty.Name + Assert.Equal("enumIntegerToString", migrationType) + + ' Verify input node and map node + Dim input = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').migration.enumIntegerToString.input") + VerifyInput(input, [option], languageName) + VerifyEnumToIntegerMappings(registrationJsonObject, unifiedSettingPath, [option]) + End Sub + + Private Shared Sub VerifyMigration(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) + Dim actualMigration = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').migration") + ' Get the single property under migration + Dim migrationProperty = DirectCast(actualMigration.Children().Single(), JProperty) + Dim migrationType = migrationProperty.Name + If migrationType = "pass" Then + ' Verify input node + Dim input = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').migration.pass.input") + VerifyInput(input, [option], languageName) + Else + ' Need adding more migration types if new type is added + Throw ExceptionUtilities.UnexpectedValue(migrationType) + End If + End Sub + + ' Verify input property under migration + Private Shared Sub VerifyInput(input As JToken, [option] As IOption2, languageName As String) + Dim store = input.SelectToken("store").ToString() + Dim path = input.SelectToken("path").ToString() + Dim configName = [option].Definition.ConfigName + Dim visualStudioStorage = Storages(configName) + If TypeOf visualStudioStorage Is VisualStudioOptionStorage.RoamingProfileStorage Then + Dim roamingProfileStorage = DirectCast(visualStudioStorage, VisualStudioOptionStorage.RoamingProfileStorage) + Assert.Equal("SettingsManager", store) + Assert.Equal(roamingProfileStorage.Key.Replace("%LANGUAGE%", GetSubstituteLanguage(languageName)), path) + Else + ' Not supported yet + Throw ExceptionUtilities.Unreachable + End If + End Sub + + Private Shared Function GetSubstituteLanguage(languageName As String) As String + Select Case languageName + Case LanguageNames.CSharp + Return "CSharp" + Case LanguageNames.VisualBasic + Return "VisualBasic" + Case Else + Return languageName + End Select + End Function + + Private Sub VerifyEnumToIntegerMappings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + ' Here we are going to verify a structure like this: + ' "map": [ + ' { + ' "result": "neverInclude", + ' "match": 1 + ' }, + ' // '0' matches to SnippetsRule.Default. Means the behavior is decided by language. + ' // '2' matches to SnippetsRule.AlwaysInclude. It's the default behavior for C# + ' // Put both mapping here, so it's possible for unified setting to load '0' from the storage. + ' // Put '2' in front, so unified settings would persist '2' to storage when 'alwaysInclude' is selected. + ' { + ' "result": "alwaysInclude", + ' "match": 2 + ' }, + ' { + ' "result": "alwaysInclude", + ' "match": 0 + ' }, + ' { + ' "result": "includeAfterTypingIdentifierQuestionTab", + ' "match": 3 + ' } + ' ] + Dim actualMappings = CType(registrationJsonObject.SelectToken(String.Format("$.properties['{0}'].migration.enumIntegerToString.map", unifiedSettingPath)), JArray).Select(Function(mapping) (mapping("result").ToString(), Integer.Parse(mapping("match").ToString()))).ToArray() + + Dim enumValues = [option].Type.GetEnumValues().Cast(Of Object).ToDictionary( + keySelector:=Function(enumValue) enumValue.ToString().ToCamelCase(), + elementSelector:=Function(enumValue) + Dim actualDefaultValue = GetOptionsDefaultValue([option]) + If actualDefaultValue.Equals(enumValue) Then + ' This value is the real default value at runtime. + ' So map it to both default value and its own value. + ' Like 'alwaysInclude' in the above example, it would map to both 0 and 2. + Return New Integer() {CInt(enumValue), CInt([option].DefaultValue)} + End If + + Return New Integer() {CInt(enumValue)} + End Function + ) + + For Each tuple In actualMappings + Dim result = tuple.Item1 + Dim match = tuple.Item2 + Dim acceptableValues = enumValues(result) + Assert.Contains(match, acceptableValues) + Next + + ' If the default value of the enum is a stub value, verify the real value mapping is put in font of the default value mapping. + ' It makes sure the default value would be converted to the real value by unified settings engine. + Dim realDefaultValue = GetOptionsDefaultValue([option]) + Dim indexOfTheRealDefaultMapping = Array.IndexOf(actualMappings, (realDefaultValue.ToString().ToCamelCase(), CInt(realDefaultValue))) + Assert.NotEqual(-1, indexOfTheRealDefaultMapping) + Dim indexOfTheDefaultMapping = Array.IndexOf(actualMappings, (realDefaultValue.ToString().ToCamelCase(), CInt([option].DefaultValue))) + Assert.NotEqual(-1, indexOfTheDefaultMapping) + Assert.True(indexOfTheRealDefaultMapping < indexOfTheDefaultMapping) + End Sub + End Class +End Namespace diff --git a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb new file mode 100644 index 0000000000000..b253bc0db5914 --- /dev/null +++ b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.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 Microsoft.CodeAnalysis + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings + Partial Public Class UnifiedSettingsTests + + ' Mapping from the config name to its path in Unified Settings registration file + Private Shared ReadOnly s_unifiedSettingsStorage As New Dictionary(Of String, UnifiedSettingsStorage)() From { + {"dotnet_trigger_completion_on_typing_letters", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.triggerCompletionOnTypingLetters")}, + {"dotnet_trigger_completion_on_deletion", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.triggerCompletionOnDeletion")}, + {"dotnet_trigger_completion_in_argument_lists", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.triggerCompletionInArgumentLists")}, + {"dotnet_highlight_matching_portions_of_completion_list_items", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.highlightMatchingPortionsOfCompletionListItems")}, + {"dotnet_show_completion_item_filters", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.showCompletionItemFilters")}, + {"csharp_complete_statement_on_semicolon", New UnifiedSettingsStorage("textEditor.csharp.intellisense.completeStatementOnSemicolon")}, + {"dotnet_snippets_behavior", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.snippetsBehavior")}, + {"dotnet_return_key_completion_behavior", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.returnKeyCompletionBehavior")}, + {"dotnet_show_name_completion_suggestions", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.showNameCompletionSuggestions")}, + {"dotnet_show_completion_items_from_unimported_namespaces", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.showCompletionItemsFromUnimportedNamespaces")}, + {"dotnet_enable_argument_completion_snippets", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.enableArgumentCompletionSnippets")}, + {"dotnet_show_new_snippet_experience", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.showNewSnippetExperience")} + } + + Friend NotInheritable Class UnifiedSettingsStorage + Private Const LanguagePlaceholder As String = "%LANGUAGE%" + + ' C# name used in Unified Settings path. + Private Const csharpKey As String = "csharp" + + ' Visual Basic name used in Unified Settings path. + Private Const visualBasicKey As String = "basic" + + ' Unified settings base path, might contains %LANGAUGE% if it maps to two per-language different setting. + Public Property UnifiedSettingsBasePath As String + + Public Sub New(unifiedSettingsPath As String) + UnifiedSettingsBasePath = unifiedSettingsPath + End Sub + + Public Function GetUnifiedSettingsPath(language As String) As String + If Not UnifiedSettingsBasePath.Contains(LanguagePlaceholder) Then + Return UnifiedSettingsBasePath + End If + + Select Case language + Case LanguageNames.CSharp + Return UnifiedSettingsBasePath.Replace(LanguagePlaceholder, csharpKey) + Case LanguageNames.VisualBasic + Return UnifiedSettingsBasePath.Replace(LanguagePlaceholder, visualBasicKey) + Case Else + Throw New Exception("Unexpected language value") + End Select + End Function + End Class + End Class +End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx index feb1994aec76a..b0aabb98df28e 100644 --- a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx +++ b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx @@ -244,15 +244,6 @@ _Never add new line on enter - - Always include snippets - - - Include snippets when ?-Tab is typed after an identifier - - - Never include snippets - Snippets behavior diff --git a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb index 046f984628ff0..f03d6a481434d 100644 --- a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb +++ b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb @@ -38,6 +38,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic + Friend NotInheritable Class VisualBasicPackage Inherits AbstractPackage(Of VisualBasicPackage, VisualBasicLanguageService) diff --git a/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj b/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj index b9cc5973ed78e..b94764b606b87 100644 --- a/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj +++ b/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj @@ -14,6 +14,12 @@ true false + + + PreserveNewest + true + + @@ -44,12 +50,12 @@ - + - + true VSPackage Designer diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml index 4d762d6809e6f..ceaccb9523848 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml +++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml @@ -53,25 +53,46 @@ - - - + Content="{x:Static local:AdvancedOptionPageStrings.Option_run_code_analysis_in_separate_process}" /> - + + - + + + + + + + + + + + + + + @@ -175,6 +196,8 @@ Content="{x:Static local:AdvancedOptionPageStrings.Option_Show_guides_for_declaration_level_constructs}" /> + @@ -205,8 +228,8 @@ Content="{x:Static local:AdvancedOptionPageStrings.Option_Report_invalid_placeholders_in_string_dot_format_calls}" /> - + diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb index 899bb86301916..4b231346694b5 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb @@ -60,18 +60,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options ' Analysis BindToOption(Run_background_code_analysis_for, SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic, label:=Run_background_code_analysis_for_label) - BindToOption(Analyze_source_generated_files, SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles, - Function() - ' If the option has not been set by the user, check if the option is enabled from experimentation. - Return optionStore.GetOption(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFilesFeatureFlag) - End Function) BindToOption(Show_compiler_errors_and_warnings_for, SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.VisualBasic) BindToOption(DisplayDiagnosticsInline, InlineDiagnosticsOptionsStorage.EnableInlineDiagnostics, LanguageNames.VisualBasic) BindToOption(at_the_end_of_the_line_of_code, InlineDiagnosticsOptionsStorage.Location, InlineDiagnosticsLocations.PlacedAtEndOfCode, LanguageNames.VisualBasic) BindToOption(on_the_right_edge_of_the_editor_window, InlineDiagnosticsOptionsStorage.Location, InlineDiagnosticsLocations.PlacedAtEndOfEditor, LanguageNames.VisualBasic) BindToOption(Run_code_analysis_in_separate_process, RemoteHostOptionsStorage.OOP64Bit) - BindToOption(Run_code_analysis_on_dotnet, RemoteHostOptionsStorage.OOPCoreClr) BindToOption(Enable_file_logging_for_diagnostics, VisualStudioLoggingOptionsStorage.EnableFileLoggingForDiagnostics) BindToOption(Skip_analyzers_for_implicitly_triggered_builds, FeatureOnOffOptions.SkipAnalyzersForImplicitlyTriggeredBuilds) @@ -81,6 +75,30 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options Return optionStore.GetOption(FeatureOnOffOptions.OfferRemoveUnusedReferencesFeatureFlag) End Function) + ' Source Generators + BindToOption(Automatic_Run_generators_after_any_change, WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic, + Function() + ' If the option hasn't been set by the user, then check the feature flag. If the feature flag has set + ' us to only run when builds complete, then we're not in automatic mode. So we `!` the result. + Return Not optionStore.GetOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecutionBalancedFeatureFlag) + End Function) + BindToOption(Balanced_Run_generators_after_saving_or_building, WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Balanced, + Function() + ' If the option hasn't been set by the user, then check the feature flag. If the feature flag has set + ' us to only run when builds complete, then we're in `Balanced_Run_generators_after_saving_or_building` mode and directly return it. + Return optionStore.GetOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecutionBalancedFeatureFlag) + End Function) + BindToOption(Analyze_source_generated_files, SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles, + Function() + ' If the option has not been set by the user, check if the option is enabled from experimentation. + Return optionStore.GetOption(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFilesFeatureFlag) + End Function) + BindToOption(Enable_all_features_in_opened_files_from_source_generators, WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspace, + Function() + ' If the option has Not been set by the user, check if the option is enabled from experimentation. + Return optionStore.GetOption(WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag) + End Function) + ' Rename BindToOption(Rename_asynchronously_exerimental, InlineRenameSessionOptionsStorage.RenameAsynchronously) BindToOption(Rename_UI_setting, InlineRenameUIOptionsStorage.UseInlineAdornment, label:=Rename_UI_setting_label) @@ -114,6 +132,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options ' Block structure guides BindToOption(Show_guides_for_declaration_level_constructs, BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.VisualBasic) BindToOption(Show_guides_for_code_level_constructs, BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.VisualBasic) + BindToOption(Show_guides_for_comments_and_preprocessor_regions, BlockStructureOptionsStorage.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, LanguageNames.VisualBasic) ' Comments BindToOption(GenerateXmlDocCommentsForTripleApostrophes, DocumentationCommentOptionsStorage.AutoXmlDocCommentGeneration, LanguageNames.VisualBasic) @@ -127,14 +146,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options BindToOption(ShowRemarksInQuickInfo, QuickInfoOptionsStorage.ShowRemarksInQuickInfo, LanguageNames.VisualBasic) BindToOption(Report_invalid_placeholders_in_string_dot_format_calls, IdeAnalyzerOptionsStorage.ReportInvalidPlaceholdersInStringDotFormatCalls, LanguageNames.VisualBasic) BindToOption(Underline_reassigned_variables, ClassificationOptionsStorage.ClassifyReassignedVariables, LanguageNames.VisualBasic) + BindToOption(Strike_out_obsolete_symbols, ClassificationOptionsStorage.ClassifyObsoleteSymbols, LanguageNames.VisualBasic) ' Go To Definition BindToOption(NavigateToObjectBrowser, VisualStudioNavigationOptionsStorage.NavigateToObjectBrowser, LanguageNames.VisualBasic) - BindToOption(Enable_all_features_in_opened_files_from_source_generators, WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspace, - Function() - ' If the option has Not been set by the user, check if the option is enabled from experimentation. - Return optionStore.GetOption(WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag) - End Function) ' Regular expressions BindToOption(Colorize_regular_expressions, ClassificationOptionsStorage.ColorizeRegexPatterns, LanguageNames.VisualBasic) @@ -241,13 +256,5 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options Collapse_sourcelink_embedded_decompiled_files_on_open.IsEnabled = False Collapse_metadata_signature_files_on_open.IsEnabled = False End Sub - - Private Sub Run_code_analysis_in_separate_process_Checked(sender As Object, e As RoutedEventArgs) - Run_code_analysis_on_dotnet.IsEnabled = True - End Sub - - Private Sub Run_code_analysis_in_separate_process_Unchecked(sender As Object, e As RoutedEventArgs) - Run_code_analysis_on_dotnet.IsEnabled = False - End Sub End Class End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb index 545513dcf7094..aae560e647242 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb @@ -76,15 +76,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options Public ReadOnly Property Option_run_code_analysis_in_separate_process As String = ServicesVSResources.Run_code_analysis_in_separate_process_requires_restart - Public ReadOnly Property Option_run_code_analysis_on_dotnet As String = - ServicesVSResources.Run_code_analysis_on_latest_dotnet_requires_restart - 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_Strike_out_obsolete_symbols As String = + ServicesVSResources.Strike_out_obsolete_symbols + Public ReadOnly Property Option_Display_all_hints_while_pressing_Alt_F1 As String = ServicesVSResources.Display_all_hints_while_pressing_Alt_F1 @@ -258,6 +258,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options Public ReadOnly Property Option_Show_guides_for_code_level_constructs As String = ServicesVSResources.Show_guides_for_code_level_constructs + Public ReadOnly Property Option_Show_guides_for_comments_and_preprocessor_regions As String = + ServicesVSResources.Show_guides_for_comments_and_preprocessor_regions + Public ReadOnly Property Option_Fading As String = ServicesVSResources.Fading @@ -386,5 +389,18 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options Public ReadOnly Property Option_Enable_document_outline_experimental_requires_restart As String = ServicesVSResources.Enable_document_outline_experimental_requires_restart + + Public ReadOnly Property Option_Source_Generators As String = + ServicesVSResources.Source_Generators + + Public ReadOnly Property Option_Source_generator_execution_requires_restart As String = + ServicesVSResources.Source_generator_execution_requires_restart + + Public ReadOnly Property Option_Automatic_Run_generators_after_any_change As String = + ServicesVSResources.Automatic_Run_generators_after_any_change + + Public ReadOnly Property Option_Balanced_Run_generators_after_saving_or_building As String = + ServicesVSResources.Balanced_Run_generators_after_saving_or_building + End Module End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb index 26f9bfa584311..7a28520e808f1 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb @@ -42,8 +42,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options End Sub Private Sub SetEnterKeyDefaultBehavior() - Dim snippetValue = Me.OptionStore.GetOption(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.VisualBasic) - If snippetValue = SnippetsRule.Default Then + Dim enterKeyRule = Me.OptionStore.GetOption(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.VisualBasic) + If enterKeyRule = EnterKeyRule.Default Then Always_add_new_line_on_enter.IsChecked = True End If End Sub diff --git a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb index e3b00fcebfd41..39b3bf040f610 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb @@ -35,13 +35,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options BasicVSResources.Snippets_behavior Public ReadOnly Property Option_Never_include_snippets As String = - BasicVSResources.Never_include_snippets + VSPackage.Never_include_snippets Public ReadOnly Property Option_Always_include_snippets As String = - BasicVSResources.Always_include_snippets + VSPackage.Always_include_snippets Public ReadOnly Property Option_Include_snippets_when_question_Tab_is_typed_after_an_identifier As String = - BasicVSResources.Include_snippets_when_Tab_is_typed_after_an_identifier + VSPackage.Include_snippets_when_Tab_is_typed_after_an_identifier Public ReadOnly Property Option_Show_items_from_unimported_namespaces As String = BasicVSResources.Show_items_from_unimported_namespaces diff --git a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef index bfb3a04c6856c..088ecdbfeb727 100644 --- a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef @@ -188,4 +188,11 @@ "Indent Style"=dword:00000002 [$RootKey$\VB Editor\Roslyn] -"DisplayLineSeparators"=dword:00000001 \ No newline at end of file +"DisplayLineSeparators"=dword:00000001 + +// CacheTag value should be changed when registration file changes +// See https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/39345/Manifest-Build-Deployment-and-Setup-Authoring-In-Depth?anchor=example-pkgdef-key for more infomation +[$RootKey$\SettingsManifests\{574fc912-f74f-4b4e-92c3-f695c208a2bb}] +@="Microsoft.VisualStudio.LanguageServices.VisualBasic.VisualBasicPackage" +"ManifestPath"="$PackageFolder$\UnifiedSettings\visualBasicSettings.registration.json" +"CacheTag"=qword:5DE8496A8900B809 diff --git a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json new file mode 100644 index 0000000000000..c744cc9dc0357 --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json @@ -0,0 +1,185 @@ +// NOTE: +// When this file is changed. Please also update the cache tag under settings entry in src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef +// Otherwise your change might be ignored. +// See https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/39345/Manifest-Build-Deployment-and-Setup-Authoring-In-Depth?anchor=example-pkgdef-key for more details +{ + "properties": { + // CompletionOptionsStorage.TriggerOnTypingLetters + "textEditor.basic.intellisense.triggerCompletionOnTypingLetters": { + "title": "@Show_completion_list_after_a_character_is_typed;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "boolean", + "default": true, + "order": 0, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.TriggerOnTypingLetters" + } + } + } + }, + // CompletionOptionsStorage.TriggerOnDeletion + "textEditor.basic.intellisense.triggerCompletionOnDeletion": { + "title": "@Show_completion_list_after_a_character_is_deleted;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "boolean", + "default": true, + "order": 1, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.TriggerOnDeletion" + } + } + } + }, + // CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems + "textEditor.basic.intellisense.highlightMatchingPortionsOfCompletionListItems": { + "title": "@Highlight_matching_portions_of_completion_list_items;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "boolean", + "default": true, + "order": 10, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.HighlightMatchingPortionsOfCompletionListItems" + } + } + } + }, + // CompletionViewOptionsStorage.ShowCompletionItemFilters + "textEditor.basic.intellisense.showCompletionItemFilters": { + "title": "@Show_completion_item_filters;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "boolean", + "default": true, + "order": 20, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.ShowCompletionItemFilters" + } + } + } + }, + // CompletionOptionsStorage.SnippetsBehavior + "textEditor.basic.intellisense.snippetsBehavior": { + "title": "@Snippets_behavior;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "string", + "enum": [ "neverInclude", "alwaysInclude", "includeAfterTypingIdentifierQuestionTab" ], + "enumItemLabels": [ "@Never_include_snippets;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", "@Always_include_snippets;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", "@Include_snippets_when_Tab_is_typed_after_an_identifier;{574fc912-f74f-4b4e-92c3-f695c208a2bb}" ], + "default": "includeAfterTypingIdentifierQuestionTab", + "order": 30, + "migration": { + "enumIntegerToString": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.SnippetsBehavior" + }, + "map": [ + { + "result": "neverInclude", + "match": 1 + }, + // '0' matches to SnippetsRule.Default. Means the behavior is decided by language. + // '3' matches to SnippetsRule.IncludeAfterTypingIdentifierQuestionTab. It's the default behavior for Visual Basic + // Put both mapping here, so it's possible for unified setting to load '0' from the storage. + // Put '3' in front, so unifed settings would persist '3' to storage when 'includeAfterTypingIdentifierQuestionTab' is selected. + { + "result": "alwaysInclude", + "match": 2 + }, + { + "result": "includeAfterTypingIdentifierQuestionTab", + "match": 3 + }, + { + "result": "includeAfterTypingIdentifierQuestionTab", + "match": 0 + } + ] + } + } + }, + // CompletionOptionsStorage.EnterKeyBehavior + "textEditor.basic.intellisense.returnKeyCompletionBehavior": { + "title": "@Enter_key_behavior_colon;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "string", + "enum": [ "never", "afterFullyTypedWord", "always" ], + "enumItemLabels": [ "@Never_add_new_line_on_enter;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", "@Only_add_new_line_on_enter_after_end_of_fully_typed_word;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", "@Always_add_new_line_on_enter;{574fc912-f74f-4b4e-92c3-f695c208a2bb}" ], + "default": "always", + "order": 40, + "migration": { + "enumIntegerToString": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.EnterKeyBehavior" + }, + "map": [ + // '0' matches to EnterKeyRule.Default. Means the behavior is decided by langauge. + // '2' matches to EnterKeyRule.Alwasys. It's the default behavior for Visual Basic + // Put both mapping here, so it's possible for unified setting to load '0' from the storage. + // Put '2' in front, so unifed settings would persist '2' to storage when 'always' is selected. + { + "result": "never", + "match": 1 + }, + { + "result": "always", + "match": 2 + }, + { + "result": "always", + "match": 0 + }, + { + "result": "afterFullyTypedWord", + "match": 3 + } + ] + } + } + }, + // CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces + "textEditor.basic.intellisense.showCompletionItemsFromUnimportedNamespaces": { + "title": "@Show_items_from_unimported_namespaces;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "boolean", + "default": true, + "order": 50, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.ShowItemsFromUnimportedNamespaces" + } + } + } + }, + // CompletionViewOptionsStorage.EnableArgumentCompletionSnippets + "textEditor.basic.intellisense.enableArgumentCompletionSnippets": { + "title": "@Tab_twice_to_insert_arguments;..\\Microsoft.VisualStudio.LanguageServices.dll", + "type": "boolean", + "default": false, + "order": 60, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.EnableArgumentCompletionSnippets" + } + } + } + } + }, + "categories": { + "textEditor.basic":{ + "title": "Visual Basic" + }, + "textEditor.basic.intellisense": { + "title": "@112;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", + "legacyOptionPageId": "04460A3B-1B5F-4402-BC6D-89A4F6F0A8D7" + } + } +} diff --git a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx index a77f0471bc1f1..00e7dc9bbac3c 100644 --- a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx +++ b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx @@ -208,4 +208,22 @@ Use enhanced colors;Editor Color Scheme;Inheritance Margin;Import Directives;Visual Basic Tools Help > About + + Always add new line on enter + + + Always include snippets + + + Include snippets when ?-Tab is typed after an identifier + + + Never add new line on enter + + + Never include snippets + + + Only add new line on enter after end of fully typed word + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf index 84a1def04bec8..a095b351cce37 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf @@ -272,21 +272,6 @@ _Při stisku Enter nikdy nepřidávat nový řádek - - Always include snippets - Vždy zahrnovat fragmenty - - - - Include snippets when ?-Tab is typed after an identifier - Zahrnovat fragmenty po zadání ?-Tab za identifikátor - - - - Never include snippets - Nikdy nezahrnovat fragmenty - - Snippets behavior Chování fragmentů diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf index 25dd17f7e540f..63b9d71ab0553 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf @@ -272,21 +272,6 @@ _Nie neue Zeile beim Drücken der EINGABETASTE einfügen - - Always include snippets - Schnipsel immer einschließen - - - - Include snippets when ?-Tab is typed after an identifier - Schnipsel einschließen, wenn ?-TAB nach einem Bezeichner eingegeben wird - - - - Never include snippets - Schnipsel nie einschließen - - Snippets behavior Schnipselverhalten diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf index b9fac78124ec8..ecad7b68a9f27 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf @@ -272,21 +272,6 @@ _No agregar nunca una nueva línea al presionar Entrar - - Always include snippets - Incluir siempre fragmentos de código - - - - Include snippets when ?-Tab is typed after an identifier - Incluir fragmentos de código cuando ?-Tab se escriba después de un identificador - - - - Never include snippets - No incluir nunca fragmentos de código - - Snippets behavior Comportamiento de los fragmentos de código diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf index bc7b5d5411ccb..244dafeaadc9e 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf @@ -272,21 +272,6 @@ _Ne jamais ajouter de nouvelle ligne après Entrée - - Always include snippets - Toujours inclure les extraits de code - - - - Include snippets when ?-Tab is typed after an identifier - Inclure les extraits de code quand ?-Tab est typé après un identificateur - - - - Never include snippets - Ne jamais inclure d'extrait de code - - Snippets behavior Comportement des extraits de code diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf index 2875c1089924e..b8cbfe8759d4c 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf @@ -272,21 +272,6 @@ _Non aggiungere mai una nuova riga dopo INVIO - - Always include snippets - Includi sempre i frammenti - - - - Include snippets when ?-Tab is typed after an identifier - Includi i frammenti quando si digita ?+TAB dopo un identificatore - - - - Never include snippets - Non includere mai i frammenti - - Snippets behavior Comportamento dei frammenti diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf index 0d5f609496cd6..b12555f31c426 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf @@ -272,21 +272,6 @@ Enter キーで新しい行を追加しない(_N) - - Always include snippets - 常にスニペットを含める - - - - Include snippets when ?-Tab is typed after an identifier - 識別子の後に ? Tab を入力したときにスニペットを含める - - - - Never include snippets - スニペットを含めない - - Snippets behavior スニペットの動作 diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf index 44a0191e1b8d8..19878d608dcdf 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf @@ -272,21 +272,6 @@ <Enter> 키를 누르면 새 줄 추가 안 함(_N) - - Always include snippets - 코드 조각 항상 포함 - - - - Include snippets when ?-Tab is typed after an identifier - 식별자 뒤에 ?-Tab을 입력하면 코드 조각 포함 - - - - Never include snippets - 코드 조각 포함 안 함 - - Snippets behavior 코드 조각 동작 diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf index be4079612f862..821817918bd48 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf @@ -272,21 +272,6 @@ _Nigdy nie dodawaj nowego wiersza po naciśnięciu klawisza Enter - - Always include snippets - Zawsze dołączaj fragmenty kodu - - - - Include snippets when ?-Tab is typed after an identifier - Dołącz fragmenty kodu po wpisaniu znaku ? po identyfikatorze i naciśnięciu klawisza Tab - - - - Never include snippets - Nigdy nie dołączaj fragmentów kodu - - Snippets behavior Zachowanie fragmentów kodu diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf index 12bbbe50c8e6f..71f7adbfcc571 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf @@ -272,21 +272,6 @@ _Nunca adicionar nova linha ao inserir - - Always include snippets - Sempre incluir snippets - - - - Include snippets when ?-Tab is typed after an identifier - Incluir snippets quando ?-Tab for digitado após um identificador - - - - Never include snippets - Nunca incluir snippets - - Snippets behavior Comportamento de snippets diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf index 18e80b678e73e..2f19095174378 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf @@ -272,21 +272,6 @@ _Никогда не добавлять новую строку при нажатии клавиши ВВОД - - Always include snippets - Всегда включать фрагменты кода - - - - Include snippets when ?-Tab is typed after an identifier - Включать фрагменты кода, когда после идентификатора указывается "?-Tab" - - - - Never include snippets - Никогда не включать фрагменты кода - - Snippets behavior Поведение фрагментов кода diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf index d5186e12e001b..fe333e73dae87 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf @@ -272,21 +272,6 @@ Enter tuşuna basıldığında _hiçbir zaman yeni satır ekleme - - Always include snippets - Kod parçacıklarını her zaman dahil et - - - - Include snippets when ?-Tab is typed after an identifier - Bir tanımlayıcıdan sonra ?-Tab yazılırsa kod parçacıklarını dahil et - - - - Never include snippets - Kod parçacıklarını hiçbir zaman dahil etme - - Snippets behavior Kod parçacığı davranışı diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf index 5d3359de43850..4532d2fa79a4a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf @@ -272,21 +272,6 @@ 按下 Enter 时不添加新行(_N) - - Always include snippets - 始终包含片段 - - - - Include snippets when ?-Tab is typed after an identifier - 在标识符后键入 ?-Tab 时包含片段 - - - - Never include snippets - 从不包含片段 - - Snippets behavior 片段行为 diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf index b912f0c3e7c4b..b1a8e9c40547a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf @@ -272,21 +272,6 @@ 永不在按下 Enter 鍵時加入新行(_N) - - Always include snippets - 一律包含程式碼片段 - - - - Include snippets when ?-Tab is typed after an identifier - 在識別碼後輸入 ?-Tab 時包含程式碼片段 - - - - Never include snippets - 一律不包含程式碼片段 - - Snippets behavior 程式碼片段行為 diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf index 89b8f916f76d4..276c0e8c18faf 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf @@ -130,6 +130,36 @@ Používat rozšířené barvy;Barevné schéma editoru;Okraj dědičnosti;Direk Nástroje Visual Basicu Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf index 5f0cd72bc7208..a3e9414b14f48 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf @@ -130,6 +130,36 @@ Erweiterte Farben verwenden;Editor-Farbschema;Vererbungsspielraum;Richtlinien im Visual Basic-Tools Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf index 8a092b2a86686..a318fc614937a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf @@ -130,6 +130,36 @@ Usar colores mejorados;Combinación de colores del editor;Margen de herencia;Dir Herramientas de Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf index 6f9d5251b0861..d202187102890 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf @@ -130,6 +130,36 @@ Utiliser des couleurs améliorées ; Modèle de couleurs de l’éditeur;Marge d Outils Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf index 05ead449eda01..0ffbe854456b1 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf @@ -130,6 +130,36 @@ Usare colori avanzati; Combinazione colori editor;Margine di ereditarietà;Diret Strumenti di Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf index c4cb8c5e5ff15..8b06a20801ee9 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf @@ -130,6 +130,36 @@ JSON 文字列のエディター機能の検出と提供; Visual Basic ツール Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf index b43840a0f901b..efb4258efa09a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf @@ -130,6 +130,36 @@ JSON 문자열 색상 지정, Visual Basic 도구 Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf index dfc04b5da1e7f..2a996c13f65dd 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf @@ -130,6 +130,36 @@ Używanie rozszerzonych kolorów; Schemat kolorów edytora;Margines dziedziczeni Narzędzia języka Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf index 7a64fb3e6120b..88970da2f6d56 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf @@ -130,6 +130,36 @@ Usar cores aprimoradas;Esquema de Cores do Editor;Margem de Herança;Importar Di Ferramentas do Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf index b0315632359ad..63dbfc68c0bb5 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf @@ -130,6 +130,36 @@ JSON; Инструменты Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf index 7c8826d805575..10bf015c805aa 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf @@ -130,6 +130,36 @@ Gelişmiş renkleri kullan;Düzenleyici Renk Düzeni;Devralma Kenar Boşluğu;İ Visual Basic Araçları Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf index 8e6b9e052b60d..9dc70f453cd84 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf @@ -130,6 +130,36 @@ JSON; Visual Basic 工具 Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf index 5784a3d19b718..06fd1d217bfde 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf @@ -130,6 +130,36 @@ JSON; Visual Basic 工具 Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs index 38fd06245c515..e949ef523ae1c 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs @@ -99,7 +99,7 @@ async Task CompareDocumentAsync(Document document) { lock (gate) { - output.AppendLine($"{document.FilePath}: {BitConverter.ToString(snapshotChecksum.ToArray())} : {BitConverter.ToString(fileChecksum.ToArray())}"); + output.AppendLine($"{document.FilePath}: {BitConverter.ToString([.. snapshotChecksum])} : {BitConverter.ToString([.. fileChecksum])}"); outOfDateCount++; } } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs index e7c14eb07a3eb..a1ee575ad5aed 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs @@ -55,13 +55,13 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe var document = context.Document; if (document == null) { - return locations.ToArray(); + return [.. locations]; } var xamlGoToDefinitionService = document.Project.Services.GetService(); if (xamlGoToDefinitionService == null) { - return locations.ToArray(); + return [.. locations]; } var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); @@ -83,7 +83,7 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe await Task.WhenAll(tasks).ConfigureAwait(false); - return locations.ToArray(); + return [.. locations]; } private async Task GetLocationsAsync(XamlDefinition definition, RequestContext context, CancellationToken cancellationToken) diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlRequestExecutionQueue.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlRequestExecutionQueue.cs index 95ee7a5f8acd7..37444936572e2 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlRequestExecutionQueue.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlRequestExecutionQueue.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CommonLanguageServerProtocol.Framework; +using Newtonsoft.Json.Linq; using Roslyn.LanguageServer.Protocol; namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer @@ -23,15 +24,14 @@ public XamlRequestExecutionQueue( _projectService = projectService; } - protected override string GetLanguageForRequest(string methodName, TRequest request) + [Obsolete] + protected internal override void BeforeRequest(TRequest request) { if (request is ITextDocumentParams textDocumentParams && textDocumentParams.TextDocument is { Uri: { IsAbsoluteUri: true } documentUri }) { _projectService.TrackOpenDocument(documentUri.LocalPath); } - - return base.GetLanguageForRequest(methodName, request); } } } diff --git a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs index cb88d9ebfcf3a..8b7163113b91b 100644 --- a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs +++ b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs @@ -208,11 +208,11 @@ private static bool TryClassifySymbol( token = name.GetNameToken(); classifiedSpan = new ClassifiedSpan(token.Span, GetClassificationForMethod(methodSymbol)); return true; - case IPropertySymbol _: + case IPropertySymbol: token = name.GetNameToken(); classifiedSpan = new ClassifiedSpan(token.Span, ClassificationTypeNames.PropertyName); return true; - case IEventSymbol _: + case IEventSymbol: token = name.GetNameToken(); classifiedSpan = new ClassifiedSpan(token.Span, ClassificationTypeNames.EventName); return true; @@ -229,7 +229,7 @@ private static bool TryClassifySymbol( token = name.GetNameToken(); classifiedSpan = new ClassifiedSpan(token.Span, GetClassificationForLocal(localSymbol)); return true; - case ILabelSymbol _: + case ILabelSymbol: token = name.GetNameToken(); classifiedSpan = new ClassifiedSpan(token.Span, ClassificationTypeNames.LabelName); return true; diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index 4b687b6a306e8..7512194ba3485 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -22,6 +22,8 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CSharpSyntaxTokens; + [ExportLanguageService(typeof(SyntaxGenerator), LanguageNames.CSharp), Shared] internal sealed class CSharpSyntaxGenerator : SyntaxGenerator { @@ -60,11 +62,11 @@ internal override SyntaxToken CreateInterpolatedStringStartToken(bool isVerbatim return isVerbatim ? SyntaxFactory.Token(default, SyntaxKind.InterpolatedVerbatimStringStartToken, InterpolatedVerbatimText, InterpolatedVerbatimText, default) - : SyntaxFactory.Token(SyntaxKind.InterpolatedStringStartToken); + : InterpolatedStringStartToken; } internal override SyntaxToken CreateInterpolatedStringEndToken() - => SyntaxFactory.Token(SyntaxKind.InterpolatedStringEndToken); + => InterpolatedStringEndToken; internal override SeparatedSyntaxList SeparatedList(IEnumerable nodes, IEnumerable separators) => SyntaxFactory.SeparatedList(nodes, separators); @@ -84,7 +86,7 @@ internal override SyntaxNode DocumentationCommentTrivia(IEnumerable var docTrivia = SyntaxFactory.DocumentationCommentTrivia( SyntaxKind.MultiLineDocumentationCommentTrivia, (SyntaxList)SyntaxFactory.List(nodes), - SyntaxFactory.Token(SyntaxKind.EndOfDocumentationCommentToken)); + EndOfDocumentationCommentToken); docTrivia = docTrivia.WithLeadingTrivia(SyntaxFactory.DocumentationCommentExterior("/// ")) .WithTrailingTrivia(trailingTrivia); @@ -196,13 +198,13 @@ private protected override SyntaxNode ParameterDeclaration( { var modifiers = CSharpSyntaxGeneratorInternal.GetParameterModifiers(refKind); if (isScoped) - modifiers = modifiers.Insert(0, SyntaxFactory.Token(SyntaxKind.ScopedKeyword)); + modifiers = modifiers.Insert(0, ScopedKeyword); if (isExtension) - modifiers = modifiers.Insert(0, SyntaxFactory.Token(SyntaxKind.ThisKeyword)); + modifiers = modifiers.Insert(0, ThisKeyword); if (isParams) - modifiers = modifiers.Add(SyntaxFactory.Token(SyntaxKind.ParamsKeyword)); + modifiers = modifiers.Add(ParamsKeyword); return SyntaxFactory.Parameter( default, @@ -220,11 +222,11 @@ internal static SyntaxToken GetArgumentModifiers(RefKind refKind) case RefKind.In: return default; case RefKind.Out: - return SyntaxFactory.Token(SyntaxKind.OutKeyword); + return OutKeyword; case RefKind.Ref: - return SyntaxFactory.Token(SyntaxKind.RefKeyword); + return RefKeyword; case RefKind.RefReadOnlyParameter: - return SyntaxFactory.Token(SyntaxKind.InKeyword); + return InKeyword; default: throw ExceptionUtilities.UnexpectedValue(refKind); } @@ -249,7 +251,7 @@ private protected override SyntaxNode MethodDeclaration( return SyntaxFactory.MethodDeclaration( attributeLists: default, modifiers: AsModifierList(accessibility, modifiers, SyntaxKind.MethodDeclaration), - returnType: returnType != null ? (TypeSyntax)returnType : SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), + returnType: returnType != null ? (TypeSyntax)returnType : SyntaxFactory.PredefinedType(VoidKeyword), explicitInterfaceSpecifier: null, identifier: name.ToIdentifierToken(), typeParameterList: AsTypeParameterList(typeParameters), @@ -257,7 +259,7 @@ private protected override SyntaxNode MethodDeclaration( constraintClauses: default, body: hasBody ? CreateBlock(statements) : null, expressionBody: null, - semicolonToken: !hasBody ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : default); + semicolonToken: !hasBody ? SemicolonToken : default); } private static string StripExplicitInterfaceName(string name) @@ -268,62 +270,85 @@ private static string StripExplicitInterfaceName(string name) } public override SyntaxNode OperatorDeclaration(OperatorKind kind, IEnumerable? parameters = null, SyntaxNode? returnType = null, Accessibility accessibility = Accessibility.NotApplicable, DeclarationModifiers modifiers = default, IEnumerable? statements = null) + { + return OperatorDeclaration(GetOperatorName(kind), isImplicitConversion: kind == OperatorKind.ImplicitConversion, parameters, returnType, accessibility, modifiers, statements); + + } + + private protected override SyntaxNode OperatorDeclaration(string operatorName, bool isImplicitConversion, IEnumerable? parameters = null, SyntaxNode? returnType = null, Accessibility accessibility = Accessibility.NotApplicable, DeclarationModifiers modifiers = default, IEnumerable? statements = null) { var hasBody = !modifiers.IsAbstract && (!modifiers.IsPartial || statements != null); - var returnTypeNode = returnType != null ? (TypeSyntax)returnType : SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)); + var returnTypeNode = returnType != null ? (TypeSyntax)returnType : SyntaxFactory.PredefinedType(VoidKeyword); var parameterList = AsParameterList(parameters); var body = hasBody ? CreateBlock(statements) : null; - var semicolon = !hasBody ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : default; + var semicolon = !hasBody ? SemicolonToken : default; var modifierList = AsModifierList(accessibility, modifiers, SyntaxKind.OperatorDeclaration); var attributes = default(SyntaxList); - if (kind is OperatorKind.ImplicitConversion or OperatorKind.ExplicitConversion) + if (operatorName is WellKnownMemberNames.ImplicitConversionName or WellKnownMemberNames.ExplicitConversionName) { + var isImplicit = operatorName is WellKnownMemberNames.ImplicitConversionName; return SyntaxFactory.ConversionOperatorDeclaration( - attributes, modifierList, SyntaxFactory.Token(GetTokenKind(kind)), - SyntaxFactory.Token(SyntaxKind.OperatorKeyword), - returnTypeNode, parameterList, body, semicolon); + attributes, modifierList, + isImplicit ? ImplicitKeyword : ExplicitKeyword, + explicitInterfaceSpecifier: null, + OperatorKeyword, + checkedKeyword: default, + returnTypeNode, parameterList, body, expressionBody: null, semicolon); } else { return SyntaxFactory.OperatorDeclaration( attributes, modifierList, returnTypeNode, - SyntaxFactory.Token(SyntaxKind.OperatorKeyword), - SyntaxFactory.Token(GetTokenKind(kind)), - parameterList, body, semicolon); + explicitInterfaceSpecifier: null, + OperatorKeyword, + checkedKeyword: CSharp.SyntaxFacts.IsCheckedOperator(operatorName) ? CheckedKeyword : default, + operatorToken: SyntaxFactory.Token(GetOperatorSyntaxKind(operatorName)), + parameterList, body, expressionBody: null, semicolon); } } - private static SyntaxKind GetTokenKind(OperatorKind kind) + private static SyntaxKind GetOperatorSyntaxKind(string operatorName) + { + var operatorKind = CSharp.SyntaxFacts.GetOperatorKind(operatorName); + if (operatorKind == SyntaxKind.None) + { + throw new ArgumentException("Unknown operator kind."); + } + + return operatorKind; + } + + private static string GetOperatorName(OperatorKind kind) => kind switch { - OperatorKind.ImplicitConversion => SyntaxKind.ImplicitKeyword, - OperatorKind.ExplicitConversion => SyntaxKind.ExplicitKeyword, - OperatorKind.Addition => SyntaxKind.PlusToken, - OperatorKind.BitwiseAnd => SyntaxKind.AmpersandToken, - OperatorKind.BitwiseOr => SyntaxKind.BarToken, - OperatorKind.Decrement => SyntaxKind.MinusMinusToken, - OperatorKind.Division => SyntaxKind.SlashToken, - OperatorKind.Equality => SyntaxKind.EqualsEqualsToken, - OperatorKind.ExclusiveOr => SyntaxKind.CaretToken, - OperatorKind.False => SyntaxKind.FalseKeyword, - OperatorKind.GreaterThan => SyntaxKind.GreaterThanToken, - OperatorKind.GreaterThanOrEqual => SyntaxKind.GreaterThanEqualsToken, - OperatorKind.Increment => SyntaxKind.PlusPlusToken, - OperatorKind.Inequality => SyntaxKind.ExclamationEqualsToken, - OperatorKind.LeftShift => SyntaxKind.LessThanLessThanToken, - OperatorKind.LessThan => SyntaxKind.LessThanToken, - OperatorKind.LessThanOrEqual => SyntaxKind.LessThanEqualsToken, - OperatorKind.LogicalNot => SyntaxKind.ExclamationToken, - OperatorKind.Modulus => SyntaxKind.PercentToken, - OperatorKind.Multiply => SyntaxKind.AsteriskToken, - OperatorKind.OnesComplement => SyntaxKind.TildeToken, - OperatorKind.RightShift => SyntaxKind.GreaterThanGreaterThanToken, - OperatorKind.UnsignedRightShift => SyntaxKind.GreaterThanGreaterThanGreaterThanToken, - OperatorKind.Subtraction => SyntaxKind.MinusToken, - OperatorKind.True => SyntaxKind.TrueKeyword, - OperatorKind.UnaryNegation => SyntaxKind.MinusToken, - OperatorKind.UnaryPlus => SyntaxKind.PlusToken, + OperatorKind.ImplicitConversion => WellKnownMemberNames.ImplicitConversionName, + OperatorKind.ExplicitConversion => WellKnownMemberNames.ExplicitConversionName, + OperatorKind.Addition => WellKnownMemberNames.AdditionOperatorName, + OperatorKind.BitwiseAnd => WellKnownMemberNames.BitwiseAndOperatorName, + OperatorKind.BitwiseOr => WellKnownMemberNames.BitwiseOrOperatorName, + OperatorKind.Decrement => WellKnownMemberNames.DecrementOperatorName, + OperatorKind.Division => WellKnownMemberNames.DivisionOperatorName, + OperatorKind.Equality => WellKnownMemberNames.EqualityOperatorName, + OperatorKind.ExclusiveOr => WellKnownMemberNames.ExclusiveOrOperatorName, + OperatorKind.False => WellKnownMemberNames.FalseOperatorName, + OperatorKind.GreaterThan => WellKnownMemberNames.GreaterThanOperatorName, + OperatorKind.GreaterThanOrEqual => WellKnownMemberNames.GreaterThanOrEqualOperatorName, + OperatorKind.Increment => WellKnownMemberNames.IncrementOperatorName, + OperatorKind.Inequality => WellKnownMemberNames.InequalityOperatorName, + OperatorKind.LeftShift => WellKnownMemberNames.LeftShiftOperatorName, + OperatorKind.LessThan => WellKnownMemberNames.LessThanOperatorName, + OperatorKind.LessThanOrEqual => WellKnownMemberNames.LessThanOrEqualOperatorName, + OperatorKind.LogicalNot => WellKnownMemberNames.LogicalNotOperatorName, + OperatorKind.Modulus => WellKnownMemberNames.ModulusOperatorName, + OperatorKind.Multiply => WellKnownMemberNames.MultiplyOperatorName, + OperatorKind.OnesComplement => WellKnownMemberNames.OnesComplementOperatorName, + OperatorKind.RightShift => WellKnownMemberNames.RightShiftOperatorName, + OperatorKind.UnsignedRightShift => WellKnownMemberNames.UnsignedRightShiftOperatorName, + OperatorKind.Subtraction => WellKnownMemberNames.SubtractionOperatorName, + OperatorKind.True => WellKnownMemberNames.TrueOperatorName, + OperatorKind.UnaryNegation => WellKnownMemberNames.UnaryNegationOperatorName, + OperatorKind.UnaryPlus => WellKnownMemberNames.UnaryPlusOperatorName, _ => throw new ArgumentException("Unknown operator kind."), }; @@ -414,7 +439,7 @@ private static SyntaxNode AccessorDeclaration( AsModifierList(accessibility, DeclarationModifiers.None, SyntaxKind.PropertyDeclaration)); accessor = statements == null - ? accessor.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + ? accessor.WithSemicolonToken(SemicolonToken) : accessor.WithBody(CreateBlock(statements)); return accessor; @@ -462,14 +487,10 @@ public override SyntaxNode IndexerDeclaration( else { if (getAccessorStatements == null && hasGetter) - { - getAccessorStatements = SpecializedCollections.EmptyEnumerable(); - } + getAccessorStatements = []; if (setAccessorStatements == null && hasSetter) - { - setAccessorStatements = SpecializedCollections.EmptyEnumerable(); - } + setAccessorStatements = []; } if (hasGetter) @@ -508,7 +529,7 @@ private static AccessorDeclarationSyntax AccessorDeclaration(SyntaxKind kind, IE if (statements == null) { - ad = ad.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + ad = ad.WithSemicolonToken(SemicolonToken); } return ad; @@ -545,8 +566,8 @@ public override SyntaxNode CustomEventDeclaration( } else { - addAccessorStatements ??= SpecializedCollections.EmptyEnumerable(); - removeAccessorStatements ??= SpecializedCollections.EmptyEnumerable(); + addAccessorStatements ??= []; + removeAccessorStatements ??= []; } accessors.Add(AccessorDeclaration(SyntaxKind.AddAccessorDeclaration, addAccessorStatements)); @@ -672,7 +693,9 @@ private static AccessorDeclarationSyntax WithBody(AccessorDeclarationSyntax acce => accessorList?.WithAccessors([.. accessorList.Accessors.Select(WithoutBody)]); private static AccessorDeclarationSyntax WithoutBody(AccessorDeclarationSyntax accessor) - => accessor.Body != null ? accessor.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)).WithBody(null) : accessor; + => accessor.Body != null ? accessor.WithSemicolonToken(SemicolonToken).WithBody(null) + : accessor.ExpressionBody != null ? accessor.WithExpressionBody(null) + : accessor; private protected override SyntaxNode ClassDeclaration( bool isRecord, @@ -702,7 +725,7 @@ private protected override SyntaxNode ClassDeclaration( var typeMembers = this.AsClassMembers(name, members); return isRecord - ? SyntaxFactory.RecordDeclaration(default, modifierList, SyntaxFactory.Token(SyntaxKind.RecordKeyword), nameToken, typeParameterList, null, baseTypeList, default, typeMembers) + ? SyntaxFactory.RecordDeclaration(default, modifierList, RecordKeyword, nameToken, typeParameterList, null, baseTypeList, default, typeMembers) : SyntaxFactory.ClassDeclaration(default, modifierList, nameToken, typeParameterList, baseTypeList, default, typeMembers); } @@ -749,7 +772,7 @@ private protected override SyntaxNode StructDeclaration( var structMembers = this.AsClassMembers(name, members); return isRecord - ? SyntaxFactory.RecordDeclaration(default, modifierList, SyntaxFactory.Token(SyntaxKind.RecordKeyword), nameToken, typeParameterList, null, baseTypeList, default, structMembers).WithClassOrStructKeyword(SyntaxFactory.Token(SyntaxKind.StructKeyword)) + ? SyntaxFactory.RecordDeclaration(default, modifierList, RecordKeyword, nameToken, typeParameterList, null, baseTypeList, default, structMembers).WithClassOrStructKeyword(StructKeyword) : SyntaxFactory.StructDeclaration(default, modifierList, nameToken, typeParameterList, baseTypeList, default, structMembers); } @@ -799,8 +822,9 @@ internal override SyntaxNode AsInterfaceMember(SyntaxNode m) case SyntaxKind.MethodDeclaration: return ((MethodDeclarationSyntax)member) .WithModifiers(default) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) - .WithBody(null); + .WithSemicolonToken(SemicolonToken) + .WithBody(null) + .WithExpressionBody(null); case SyntaxKind.OperatorDeclaration: var operatorDeclaration = (OperatorDeclarationSyntax)member; @@ -809,10 +833,10 @@ internal override SyntaxNode AsInterfaceMember(SyntaxNode m) || x.Kind() == SyntaxKind.VirtualKeyword || x.Kind() == SyntaxKind.PublicKeyword); var modifiersToken = SyntaxFactory.TokenList(abstractVirtualModifiers); - modifiersToken = modifiersToken.Insert(0, SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + modifiersToken = modifiersToken.Insert(0, StaticKeyword); return operatorDeclaration .WithModifiers(modifiersToken) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + .WithSemicolonToken(SemicolonToken) .WithBody(null); case SyntaxKind.PropertyDeclaration: @@ -920,7 +944,7 @@ private protected override SyntaxNode DelegateDeclaration( return SyntaxFactory.DelegateDeclaration( default, AsModifierList(accessibility, modifiers, SyntaxKind.DelegateDeclaration), - returnType != null ? (TypeSyntax)returnType : SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), + returnType != null ? (TypeSyntax)returnType : SyntaxFactory.PredefinedType(VoidKeyword), name.ToIdentifierToken(), AsTypeParameterList(typeParameters), AsParameterList(parameters), @@ -1070,12 +1094,12 @@ private SyntaxNode InsertReturnAttributesInternal(SyntaxNode d, int index, IEnum private static IEnumerable AsReturnAttributes(IEnumerable attributes) { return AsAttributeLists(attributes) - .Select(list => list.WithTarget(SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.ReturnKeyword)))); + .Select(list => list.WithTarget(SyntaxFactory.AttributeTargetSpecifier(ReturnKeyword))); } private static SyntaxList AsAssemblyAttributes(IEnumerable attributes) { - return [.. attributes.Select(list => list.WithTarget(SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.AssemblyKeyword))))]; + return [.. attributes.Select(list => list.WithTarget(SyntaxFactory.AttributeTargetSpecifier(AssemblyKeyword)))]; } private static SyntaxList WithRequiredTargetSpecifier(SyntaxList attributes, SyntaxNode declaration) @@ -1110,7 +1134,7 @@ public override IReadOnlyList GetAttributeArguments(SyntaxNode attri break; } - return SpecializedCollections.EmptyReadOnlyList(); + return []; } public override SyntaxNode InsertAttributeArguments(SyntaxNode declaration, int index, IEnumerable attributeArguments) @@ -1200,6 +1224,9 @@ private static SyntaxNode WithAttributeLists(SyntaxNode declaration, SyntaxList< _ => declaration, }; + internal override SyntaxNode? GetPrimaryConstructorParameterList(SyntaxNode declaration) + => declaration is TypeDeclarationSyntax { ParameterList: { } parameterList } ? parameterList : null; + internal override ImmutableArray GetTypeInheritance(SyntaxNode declaration) => declaration is BaseTypeDeclarationSyntax baseType && baseType.BaseList != null ? [baseType.BaseList] @@ -1210,7 +1237,7 @@ public override IReadOnlyList GetNamespaceImports(SyntaxNode declara { CompilationUnitSyntax compilationUnit => compilationUnit.Usings, BaseNamespaceDeclarationSyntax namespaceDeclaration => namespaceDeclaration.Usings, - _ => SpecializedCollections.EmptyReadOnlyList(), + _ => [], }; public override SyntaxNode InsertNamespaceImports(SyntaxNode declaration, int index, IEnumerable imports) @@ -1239,7 +1266,7 @@ public override IReadOnlyList GetMembers(SyntaxNode declaration) EnumDeclarationSyntax @enum => @enum.Members, BaseNamespaceDeclarationSyntax @namespace => @namespace.Members, CompilationUnitSyntax compilationUnit => compilationUnit.Members, - _ => SpecializedCollections.EmptyReadOnlyList(), + _ => [], }); private static ImmutableArray Flatten(IEnumerable declarations) @@ -1317,8 +1344,8 @@ private static SyntaxNode EnsureTypeDeclarationHasBody(SyntaxNode declaration) { return typeDeclaration .WithSemicolonToken(default) - .WithOpenBraceToken(typeDeclaration.OpenBraceToken == default ? SyntaxFactory.Token(SyntaxKind.OpenBraceToken) : typeDeclaration.OpenBraceToken) - .WithCloseBraceToken(typeDeclaration.CloseBraceToken == default ? SyntaxFactory.Token(SyntaxKind.CloseBraceToken) : typeDeclaration.CloseBraceToken); + .WithOpenBraceToken(typeDeclaration.OpenBraceToken == default ? OpenBraceToken : typeDeclaration.OpenBraceToken) + .WithCloseBraceToken(typeDeclaration.CloseBraceToken == default ? CloseBraceToken : typeDeclaration.CloseBraceToken); } return declaration; @@ -1649,77 +1676,77 @@ private static SyntaxTokenList AsModifierList(Accessibility accessibility, Decla switch (accessibility) { case Accessibility.Internal: - list.Add(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); + list.Add(InternalKeyword); break; case Accessibility.Public: - list.Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + list.Add(PublicKeyword); break; case Accessibility.Private: - list.Add(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); + list.Add(PrivateKeyword); break; case Accessibility.Protected: - list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + list.Add(ProtectedKeyword); break; case Accessibility.ProtectedOrInternal: - list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - list.Add(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); + list.Add(ProtectedKeyword); + list.Add(InternalKeyword); break; case Accessibility.ProtectedAndInternal: - list.Add(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); - list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + list.Add(PrivateKeyword); + list.Add(ProtectedKeyword); break; case Accessibility.NotApplicable: break; } if (modifiers.IsFile) - list.Add(SyntaxFactory.Token(SyntaxKind.FileKeyword)); + list.Add(FileKeyword); if (modifiers.IsAbstract) - list.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); + list.Add(AbstractKeyword); if (modifiers.IsNew) - list.Add(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + list.Add(NewKeyword); if (modifiers.IsSealed) - list.Add(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); + list.Add(SealedKeyword); if (modifiers.IsOverride) - list.Add(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); + list.Add(OverrideKeyword); if (modifiers.IsVirtual) - list.Add(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); + list.Add(VirtualKeyword); if (modifiers.IsStatic) - list.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + list.Add(StaticKeyword); if (modifiers.IsAsync) - list.Add(SyntaxFactory.Token(SyntaxKind.AsyncKeyword)); + list.Add(AsyncKeyword); if (modifiers.IsConst) - list.Add(SyntaxFactory.Token(SyntaxKind.ConstKeyword)); + list.Add(ConstKeyword); if (modifiers.IsReadOnly) - list.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + list.Add(ReadOnlyKeyword); if (modifiers.IsUnsafe) - list.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + list.Add(UnsafeKeyword); if (modifiers.IsVolatile) - list.Add(SyntaxFactory.Token(SyntaxKind.VolatileKeyword)); + list.Add(VolatileKeyword); if (modifiers.IsExtern) - list.Add(SyntaxFactory.Token(SyntaxKind.ExternKeyword)); + list.Add(ExternKeyword); if (modifiers.IsRequired) - list.Add(SyntaxFactory.Token(SyntaxKind.RequiredKeyword)); + list.Add(RequiredKeyword); // partial and ref must be last if (modifiers.IsRef) - list.Add(SyntaxFactory.Token(SyntaxKind.RefKeyword)); + list.Add(RefKeyword); if (modifiers.IsPartial) - list.Add(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); + list.Add(PartialKeyword); return [.. list]; } @@ -1733,8 +1760,8 @@ private protected override SyntaxNode TypeParameter(ITypeParameterSymbol typePar attributeLists: default, varianceKeyword: typeParameter.Variance switch { - VarianceKind.In => SyntaxFactory.Token(SyntaxKind.InKeyword), - VarianceKind.Out => SyntaxFactory.Token(SyntaxKind.OutKeyword), + VarianceKind.In => InKeyword, + VarianceKind.Out => OutKeyword, _ => default, }, SyntaxFactory.Identifier(typeParameter.Name)); @@ -2126,8 +2153,8 @@ public override IReadOnlyList GetParameters(SyntaxNode declaration) return list != null ? list.Parameters : declaration is SimpleLambdaExpressionSyntax simpleLambda - ? new[] { simpleLambda.Parameter } - : SpecializedCollections.EmptyReadOnlyList(); + ? [simpleLambda.Parameter] + : []; } public override SyntaxNode InsertParameters(SyntaxNode declaration, int index, IEnumerable parameters) @@ -2146,7 +2173,7 @@ public override SyntaxNode InsertParameters(SyntaxNode declaration, int index, I public override IReadOnlyList GetSwitchSections(SyntaxNode switchStatement) { var statement = switchStatement as SwitchStatementSyntax; - return statement?.Sections ?? SpecializedCollections.EmptyReadOnlyList(); + return statement?.Sections ?? []; } public override SyntaxNode InsertSwitchSections(SyntaxNode switchStatement, int index, IEnumerable switchSections) @@ -2454,45 +2481,33 @@ private static SyntaxNode WithEqualsValue(SyntaxNode declaration, EqualsValueCla return declaration; } - private static readonly IReadOnlyList s_EmptyList = SpecializedCollections.EmptyReadOnlyList(); - public override IReadOnlyList GetStatements(SyntaxNode declaration) { - switch (declaration.Kind()) - { - case SyntaxKind.MethodDeclaration: - return ((MethodDeclarationSyntax)declaration).Body?.Statements ?? s_EmptyList; - case SyntaxKind.OperatorDeclaration: - return ((OperatorDeclarationSyntax)declaration).Body?.Statements ?? s_EmptyList; - case SyntaxKind.ConversionOperatorDeclaration: - return ((ConversionOperatorDeclarationSyntax)declaration).Body?.Statements ?? s_EmptyList; - case SyntaxKind.ConstructorDeclaration: - return ((ConstructorDeclarationSyntax)declaration).Body?.Statements ?? s_EmptyList; - case SyntaxKind.DestructorDeclaration: - return ((DestructorDeclarationSyntax)declaration).Body?.Statements ?? s_EmptyList; - case SyntaxKind.LocalFunctionStatement: - return ((LocalFunctionStatementSyntax)declaration).Body?.Statements ?? s_EmptyList; - case SyntaxKind.AnonymousMethodExpression: - return (((AnonymousMethodExpressionSyntax)declaration).Body as BlockSyntax)?.Statements ?? s_EmptyList; - case SyntaxKind.ParenthesizedLambdaExpression: - return (((ParenthesizedLambdaExpressionSyntax)declaration).Body as BlockSyntax)?.Statements ?? s_EmptyList; - case SyntaxKind.SimpleLambdaExpression: - return (((SimpleLambdaExpressionSyntax)declaration).Body as BlockSyntax)?.Statements ?? s_EmptyList; - case SyntaxKind.GetAccessorDeclaration: - case SyntaxKind.SetAccessorDeclaration: - case SyntaxKind.AddAccessorDeclaration: - case SyntaxKind.RemoveAccessorDeclaration: - return ((AccessorDeclarationSyntax)declaration).Body?.Statements ?? s_EmptyList; - default: - return s_EmptyList; - } + var result = declaration.Kind() switch + { + SyntaxKind.MethodDeclaration => ((MethodDeclarationSyntax)declaration).Body?.Statements, + SyntaxKind.OperatorDeclaration => ((OperatorDeclarationSyntax)declaration).Body?.Statements, + SyntaxKind.ConversionOperatorDeclaration => ((ConversionOperatorDeclarationSyntax)declaration).Body?.Statements, + SyntaxKind.ConstructorDeclaration => ((ConstructorDeclarationSyntax)declaration).Body?.Statements, + SyntaxKind.DestructorDeclaration => ((DestructorDeclarationSyntax)declaration).Body?.Statements, + SyntaxKind.LocalFunctionStatement => ((LocalFunctionStatementSyntax)declaration).Body?.Statements, + SyntaxKind.AnonymousMethodExpression => (((AnonymousMethodExpressionSyntax)declaration).Body as BlockSyntax)?.Statements, + SyntaxKind.ParenthesizedLambdaExpression => (((ParenthesizedLambdaExpressionSyntax)declaration).Body as BlockSyntax)?.Statements, + SyntaxKind.SimpleLambdaExpression => (((SimpleLambdaExpressionSyntax)declaration).Body as BlockSyntax)?.Statements, + SyntaxKind.GetAccessorDeclaration or + SyntaxKind.SetAccessorDeclaration or + SyntaxKind.AddAccessorDeclaration or + SyntaxKind.RemoveAccessorDeclaration => ((AccessorDeclarationSyntax)declaration).Body?.Statements, + _ => [], + }; + return result ?? []; } public override SyntaxNode WithStatements(SyntaxNode declaration, IEnumerable statements) { var body = CreateBlock(statements); var somebody = statements != null ? body : null; - var semicolon = statements == null ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : default; + var semicolon = statements == null ? SemicolonToken : default; switch (declaration.Kind()) { @@ -2527,7 +2542,7 @@ public override SyntaxNode WithStatements(SyntaxNode declaration, IEnumerable GetAccessors(SyntaxNode declaration) { var list = GetAccessorList(declaration); - return list?.Accessors ?? s_EmptyList; + return list?.Accessors ?? []; } public override SyntaxNode InsertAccessors(SyntaxNode declaration, int index, IEnumerable accessors) @@ -2631,13 +2646,13 @@ private SyntaxNode WithAccessor(SyntaxNode declaration, AccessorListSyntax? acce public override IReadOnlyList GetGetAccessorStatements(SyntaxNode declaration) { var accessor = GetAccessor(declaration, SyntaxKind.GetAccessorDeclaration); - return accessor?.Body?.Statements ?? s_EmptyList; + return accessor?.Body?.Statements ?? []; } public override IReadOnlyList GetSetAccessorStatements(SyntaxNode declaration) { var accessor = GetAccessor(declaration, SyntaxKind.SetAccessorDeclaration); - return accessor?.Body?.Statements ?? s_EmptyList; + return accessor?.Body?.Statements ?? []; } public override SyntaxNode WithGetAccessorStatements(SyntaxNode declaration, IEnumerable statements) @@ -2669,7 +2684,7 @@ public override IReadOnlyList GetBaseAndInterfaceTypes(SyntaxNode de } else { - return SpecializedCollections.EmptyReadOnlyList(); + return []; } } @@ -2968,7 +2983,7 @@ private static IReadOnlyList GetSubDeclarations(SyntaxNode declarati SyntaxKind.LocalDeclarationStatement => ((LocalDeclarationStatementSyntax)declaration).Declaration.Variables, SyntaxKind.VariableDeclaration => ((VariableDeclarationSyntax)declaration).Variables, SyntaxKind.AttributeList => ((AttributeListSyntax)declaration).Attributes, - _ => SpecializedCollections.EmptyReadOnlyList(), + _ => [], }; public override SyntaxNode RemoveNode(SyntaxNode root, SyntaxNode node) @@ -3417,7 +3432,7 @@ public override SyntaxNode QualifiedName(SyntaxNode left, SyntaxNode right) internal override SyntaxNode GlobalAliasedName(SyntaxNode name) => SyntaxFactory.AliasQualifiedName( - SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword)), + SyntaxFactory.IdentifierName(GlobalKeyword), (SimpleNameSyntax)name); public override SyntaxNode NameExpression(INamespaceOrTypeSymbol namespaceOrTypeSymbol) @@ -3429,31 +3444,31 @@ private protected override SyntaxNode TypeExpression(ITypeSymbol typeSymbol, Ref return refKind switch { RefKind.Ref => SyntaxFactory.RefType(type), - RefKind.RefReadOnly => SyntaxFactory.RefType(SyntaxFactory.Token(SyntaxKind.RefKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword), type), + RefKind.RefReadOnly => SyntaxFactory.RefType(RefKeyword, ReadOnlyKeyword, type), _ => type, }; } public override SyntaxNode TypeExpression(SpecialType specialType) - => specialType switch - { - SpecialType.System_Boolean => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)), - SpecialType.System_Byte => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ByteKeyword)), - SpecialType.System_Char => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.CharKeyword)), - SpecialType.System_Decimal => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DecimalKeyword)), - SpecialType.System_Double => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword)), - SpecialType.System_Int16 => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ShortKeyword)), - SpecialType.System_Int32 => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)), - SpecialType.System_Int64 => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.LongKeyword)), - SpecialType.System_Object => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)), - SpecialType.System_SByte => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.SByteKeyword)), - SpecialType.System_Single => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.FloatKeyword)), - SpecialType.System_String => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)), - SpecialType.System_UInt16 => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UShortKeyword)), - SpecialType.System_UInt32 => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UIntKeyword)), - SpecialType.System_UInt64 => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ULongKeyword)), + => SyntaxFactory.PredefinedType(specialType switch + { + SpecialType.System_Boolean => BoolKeyword, + SpecialType.System_Byte => ByteKeyword, + SpecialType.System_Char => CharKeyword, + SpecialType.System_Decimal => DecimalKeyword, + SpecialType.System_Double => DoubleKeyword, + SpecialType.System_Int16 => ShortKeyword, + SpecialType.System_Int32 => IntKeyword, + SpecialType.System_Int64 => LongKeyword, + SpecialType.System_Object => ObjectKeyword, + SpecialType.System_SByte => SByteKeyword, + SpecialType.System_Single => FloatKeyword, + SpecialType.System_String => StringKeyword, + SpecialType.System_UInt16 => UShortKeyword, + SpecialType.System_UInt32 => UIntKeyword, + SpecialType.System_UInt64 => ULongKeyword, _ => throw new NotSupportedException("Unsupported SpecialType"), - }; + }); public override SyntaxNode ArrayTypeExpression(SyntaxNode type) => SyntaxFactory.ArrayType((TypeSyntax)type, [SyntaxFactory.ArrayRankSpecifier()]); @@ -3540,13 +3555,13 @@ public override SyntaxNode SwitchStatement(SyntaxNode expression, IEnumerable()], - SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); + CloseBraceToken); } } diff --git a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs b/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs index 03f17834b4e67..5df82749fae48 100644 --- a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs +++ b/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs @@ -148,7 +148,7 @@ public ImmutableArray GetFormattingChangesOnTypedCharacter( return changes; } - return FormatToken(document, indentationOptions, token, formattingRules, cancellationToken).ToImmutableArray(); + return [.. FormatToken(document, indentationOptions, token, formattingRules, cancellationToken)]; } private static bool OnlySmartIndentCloseBrace(in AutoFormattingOptions options) @@ -199,7 +199,7 @@ private static ImmutableArray FormatRange( var formatter = new CSharpSmartTokenFormatter(options, formattingRules, (CompilationUnitSyntax)document.Root, document.Text); var changes = formatter.FormatRange(tokenRange.Value.Item1, tokenRange.Value.Item2, cancellationToken); - return changes.ToImmutableArray(); + return [.. changes]; } private static IEnumerable GetTypingRules(SyntaxToken tokenBeforeCaret) @@ -279,10 +279,10 @@ private static IEnumerable GetTypingRules(SyntaxToken to if (tokenBeforeCaret.Kind() is SyntaxKind.CloseBraceToken or SyntaxKind.EndOfFileToken) { - return SpecializedCollections.EmptyEnumerable(); + return []; } - return SpecializedCollections.SingletonEnumerable(TypingFormattingRule.Instance); + return [TypingFormattingRule.Instance]; } private static bool IsEndToken(SyntaxToken endToken) @@ -318,9 +318,12 @@ or SyntaxKind.EndOfDirectiveToken private ImmutableArray GetFormattingRules(ParsedDocument document, int position, SyntaxToken tokenBeforeCaret) { var formattingRuleFactory = _services.SolutionServices.GetRequiredService(); - return ImmutableArray.Create(formattingRuleFactory.CreateRule(document, position)) - .AddRange(GetTypingRules(tokenBeforeCaret)) - .AddRange(Formatter.GetDefaultFormattingRules(_services)); + return + [ + formattingRuleFactory.CreateRule(document, position), + .. GetTypingRules(tokenBeforeCaret), + .. Formatter.GetDefaultFormattingRules(_services), + ]; } public ImmutableArray GetFormattingChangesOnPaste(ParsedDocument document, TextSpan textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken) @@ -331,8 +334,8 @@ public ImmutableArray GetFormattingChangesOnPaste(ParsedDocument doc var rules = new List() { new PasteFormattingRule() }; rules.AddRange(service.GetDefaultFormattingRules()); - var result = service.GetFormattingResult(document.Root, SpecializedCollections.SingletonEnumerable(formattingSpan), options, rules, cancellationToken); - return result.GetTextChanges(cancellationToken).ToImmutableArray(); + var result = service.GetFormattingResult(document.Root, [formattingSpan], options, rules, cancellationToken); + return [.. result.GetTextChanges(cancellationToken)]; } internal sealed class PasteFormattingRule : AbstractFormattingRule diff --git a/src/Workspaces/CSharp/Portable/ObsoleteSymbol/CSharpObsoleteSymbolService.cs b/src/Workspaces/CSharp/Portable/ObsoleteSymbol/CSharpObsoleteSymbolService.cs new file mode 100644 index 0000000000000..db62ff996c5f7 --- /dev/null +++ b/src/Workspaces/CSharp/Portable/ObsoleteSymbol/CSharpObsoleteSymbolService.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.ObsoleteSymbol; + +namespace Microsoft.CodeAnalysis.CSharp.ObsoleteSymbol; + +[ExportLanguageService(typeof(IObsoleteSymbolService), LanguageNames.CSharp)] +[Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpObsoleteSymbolService() : AbstractObsoleteSymbolService(dimKeywordKind: null) +{ +} diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs index 45d57037d405e..e50eb4f4a3c07 100644 --- a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs @@ -824,7 +824,7 @@ private ImmutableArray GetUnnamedSymbols(ExpressionSyntax originalExpre AddOperators(container, symbols); AddConversions(container, symbols); - return symbols.ToImmutable(); + return symbols.ToImmutableAndClear(); } private ITypeSymbol? GetContainerForUnnamedSymbols(SemanticModel semanticModel, ExpressionSyntax originalExpression) diff --git a/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs b/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs index d863a5e46e193..23f2b73784bed 100644 --- a/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs +++ b/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs @@ -478,15 +478,15 @@ private async Task RenameAndAnnotateAsync(SyntaxToken token, Syntax } else { - symbols = SpecializedCollections.SingletonEnumerable(symbolInfo.Symbol); + symbols = [symbolInfo.Symbol]; } var renameDeclarationLocations = - ConflictResolver.CreateDeclarationLocationAnnotationsAsync( - _solution, - symbols, - _cancellationToken) - .WaitAndGetResult_CanCallOnBackground(_cancellationToken); + ConflictResolver.CreateDeclarationLocationAnnotationsAsync( + _solution, + symbols, + _cancellationToken) + .WaitAndGetResult_CanCallOnBackground(_cancellationToken); var renameAnnotation = new RenameActionAnnotation( identifierToken.Span, @@ -810,7 +810,7 @@ public override async Task> ComputeDeclarationConflicts if (renamedSymbol.ContainingSymbol is INamedTypeSymbol { TypeKind: not TypeKind.Enum } containingNamedType && containingNamedType.Name == renamedSymbol.Name) { - AddSymbolSourceSpans(conflicts, SpecializedCollections.SingletonEnumerable(containingNamedType), reverseMappedLocations); + AddSymbolSourceSpans(conflicts, [containingNamedType], reverseMappedLocations); } if (renamedSymbol.Kind is SymbolKind.Parameter or @@ -927,7 +927,7 @@ renamedSymbol.ContainingSymbol is IMethodSymbol methodSymbol && } } - return conflicts.ToImmutable(); + return conflicts.ToImmutableAndClear(); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { diff --git a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs index cc7644984d66e..104e0425bcc0e 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs @@ -24,11 +24,14 @@ namespace Microsoft.CodeAnalysis.CSharp.Simplification; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal partial class CSharpSimplificationService { private class Expander : CSharpSyntaxRewriter { - private static readonly SyntaxTrivia s_oneWhitespaceSeparator = SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " "); + private static readonly SyntaxTrivia s_oneWhitespaceSeparator = SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " "); private static readonly SymbolDisplayFormat s_typeNameFormatWithGenerics = new( @@ -224,9 +227,9 @@ public override SyntaxNode VisitSimpleLambdaExpression(SimpleLambdaExpressionSyn var typeSyntax = parameterSymbol.Type.GenerateTypeSyntax().WithTrailingTrivia(s_oneWhitespaceSeparator); var newSimpleLambdaParameter = simpleLambda.Parameter.WithType(typeSyntax).WithoutTrailingTrivia(); - var parenthesizedLambda = SyntaxFactory.ParenthesizedLambdaExpression( + var parenthesizedLambda = ParenthesizedLambdaExpression( simpleLambda.AsyncKeyword, - SyntaxFactory.ParameterList([newSimpleLambdaParameter]) + ParameterList([newSimpleLambdaParameter]) .WithTrailingTrivia(simpleLambda.Parameter.GetTrailingTrivia()) .WithLeadingTrivia(simpleLambda.Parameter.GetLeadingTrivia()), simpleLambda.ArrowToken, @@ -255,12 +258,12 @@ public override SyntaxNode VisitArgument(ArgumentSyntax node) var inferredName = node.Expression.TryGetInferredMemberName(); if (CanMakeNameExplicitInTuple(tuple, inferredName)) { - var identifier = SyntaxFactory.Identifier(inferredName); + var identifier = Identifier(inferredName); identifier = TryEscapeIdentifierToken(identifier, node); newArgument = newArgument .WithoutLeadingTrivia() - .WithNameColon(SyntaxFactory.NameColon(SyntaxFactory.IdentifierName(identifier))) + .WithNameColon(NameColon(IdentifierName(identifier))) .WithAdditionalAnnotations(Simplifier.Annotation) .WithLeadingTrivia(node.GetLeadingTrivia()); } @@ -325,12 +328,12 @@ public override SyntaxNode VisitAnonymousObjectMemberDeclarator(AnonymousObjectM if (inferredName != null) { // Creating identifier without elastic trivia to avoid unexpected line break - var identifier = SyntaxFactory.Identifier(SyntaxTriviaList.Empty, inferredName, SyntaxTriviaList.Empty); + var identifier = Identifier(SyntaxTriviaList.Empty, inferredName, SyntaxTriviaList.Empty); identifier = TryEscapeIdentifierToken(identifier, node); newDeclarator = newDeclarator .WithoutLeadingTrivia() - .WithNameEquals(SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(identifier)) + .WithNameEquals(NameEquals(IdentifierName(identifier)) .WithLeadingTrivia(node.GetLeadingTrivia())) .WithAdditionalAnnotations(Simplifier.Annotation); } @@ -394,10 +397,10 @@ public override SyntaxNode VisitNameMemberCref(NameMemberCrefSyntax node) if (rewrittenname.Kind() == SyntaxKind.QualifiedName) { - return node.CopyAnnotationsTo(SyntaxFactory.QualifiedCref( + return node.CopyAnnotationsTo(QualifiedCref( ((QualifiedNameSyntax)rewrittenname).Left .WithAdditionalAnnotations(Simplifier.Annotation), - SyntaxFactory.NameMemberCref(((QualifiedNameSyntax)rewrittenname).Right, parameters) + NameMemberCref(((QualifiedNameSyntax)rewrittenname).Right, parameters) .WithLeadingTrivia(SyntaxTriviaList.Empty)) .WithLeadingTrivia(node.GetLeadingTrivia()) .WithTrailingTrivia(node.GetTrailingTrivia())) @@ -405,7 +408,7 @@ public override SyntaxNode VisitNameMemberCref(NameMemberCrefSyntax node) } else if (rewrittenname.Kind() == SyntaxKind.AliasQualifiedName) { - return node.CopyAnnotationsTo(SyntaxFactory.TypeCref( + return node.CopyAnnotationsTo(TypeCref( rewrittenname).WithLeadingTrivia(node.GetLeadingTrivia()) .WithTrailingTrivia(node.GetTrailingTrivia())) .WithAdditionalAnnotations(Simplifier.Annotation); @@ -597,7 +600,7 @@ SyntaxToken GetNewIdentifier(SyntaxToken _identifier) identifier = identifier.WithAdditionalAnnotations(SimplificationHelpers.DoNotSimplifyAnnotation); } - identifier = identifier.CopyAnnotationsTo(SyntaxFactory.VerbatimIdentifier(identifier.LeadingTrivia, name, name, identifier.TrailingTrivia)); + identifier = identifier.CopyAnnotationsTo(VerbatimIdentifier(identifier.LeadingTrivia, name, name, identifier.TrailingTrivia)); } } @@ -688,18 +691,18 @@ SymbolKind.Field or // Assumption here is, if the enclosing and containing types are different then there is inheritance relationship if (!Equals(_semanticModel.GetEnclosingNamedType(originalSimpleName.SpanStart, _cancellationToken), symbol.ContainingType)) { - left = SyntaxFactory.BaseExpression(); + left = BaseExpression(); } else { - left = SyntaxFactory.ThisExpression(); + left = ThisExpression(); } var identifiersLeadingTrivia = newNode.GetLeadingTrivia(); newNode = TryAddTypeArgumentToIdentifierName(newNode, symbol); newNode = newNode.CopyAnnotationsTo( - SyntaxFactory.MemberAccessExpression( + MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, left, (SimpleNameSyntax)newNode.WithLeadingTrivia(null)) @@ -745,10 +748,10 @@ private static ExpressionSyntax TryAddTypeArgumentToIdentifierName(ExpressionSyn var typeArguments = ((IMethodSymbol)symbol).TypeArguments; if (!typeArguments.Any(static t => t.ContainsAnonymousType())) { - var genericName = SyntaxFactory.GenericName( + var genericName = GenericName( ((IdentifierNameSyntax)newNode).Identifier, - SyntaxFactory.TypeArgumentList( - [.. typeArguments.Select(p => SyntaxFactory.ParseTypeName(p.ToDisplayString(s_typeNameFormatWithGenerics)))])) + TypeArgumentList( + [.. typeArguments.Select(p => ParseTypeName(p.ToDisplayString(s_typeNameFormatWithGenerics)))])) .WithLeadingTrivia(newNode.GetLeadingTrivia()) .WithTrailingTrivia(newNode.GetTrailingTrivia()) .WithAdditionalAnnotations(Simplifier.Annotation); @@ -912,8 +915,8 @@ private ExpressionSyntax FullyQualifyIdentifierName( if (!replaceNode && symbol.ContainingType == null && symbol.ContainingNamespace.IsGlobalNamespace) { return rewrittenNode.CopyAnnotationsTo( - SyntaxFactory.AliasQualifiedName( - SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword)), + AliasQualifiedName( + IdentifierName(GlobalKeyword), (SimpleNameSyntax)rewrittenNode.WithLeadingTrivia(null)) .WithLeadingTrivia(rewrittenNode.GetLeadingTrivia())); } @@ -932,7 +935,7 @@ private ExpressionSyntax FullyQualifyIdentifierName( var displayString = displaySymbol.ToDisplayString(s_typeNameFormatWithGenerics); - ExpressionSyntax left = SyntaxFactory.ParseTypeName(displayString); + ExpressionSyntax left = ParseTypeName(displayString); // Replaces the '<' token with the '{' token since we are inside crefs left = TryReplaceAngleBracesWithCurlyBraces(left, isInsideCref); @@ -953,7 +956,7 @@ private ExpressionSyntax FullyQualifyIdentifierName( { case SyntaxKind.QualifiedName: result = rewrittenNode.CopyAnnotationsTo( - SyntaxFactory.QualifiedName( + QualifiedName( (NameSyntax)left, (SimpleNameSyntax)rewrittenNode)); @@ -961,7 +964,7 @@ private ExpressionSyntax FullyQualifyIdentifierName( case SyntaxKind.SimpleMemberAccessExpression: result = rewrittenNode.CopyAnnotationsTo( - SyntaxFactory.MemberAccessExpression( + MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, left, (SimpleNameSyntax)rewrittenNode)); @@ -974,17 +977,17 @@ private ExpressionSyntax FullyQualifyIdentifierName( if (SyntaxFacts.IsInNamespaceOrTypeContext(originalNode)) { var right = (SimpleNameSyntax)rewrittenNode; - result = rewrittenNode.CopyAnnotationsTo(SyntaxFactory.QualifiedName((NameSyntax)left, right.WithAdditionalAnnotations(Simplifier.SpecialTypeAnnotation))); + result = rewrittenNode.CopyAnnotationsTo(QualifiedName((NameSyntax)left, right.WithAdditionalAnnotations(Simplifier.SpecialTypeAnnotation))); } else if (originalNode.Parent is CrefSyntax) { var right = (SimpleNameSyntax)rewrittenNode; - result = rewrittenNode.CopyAnnotationsTo(SyntaxFactory.QualifiedName((NameSyntax)left, right)); + result = rewrittenNode.CopyAnnotationsTo(QualifiedName((NameSyntax)left, right)); } else { result = rewrittenNode.CopyAnnotationsTo( - SyntaxFactory.MemberAccessExpression( + MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, left, (SimpleNameSyntax)rewrittenNode)); @@ -1004,12 +1007,12 @@ private SyntaxToken ReplaceTokenForCref(SyntaxToken oldToken, SyntaxToken dummyS { if (oldToken.Kind() == SyntaxKind.LessThanToken) { - return SyntaxFactory.Token(oldToken.LeadingTrivia, SyntaxKind.LessThanToken, "{", "{", oldToken.TrailingTrivia); + return Token(oldToken.LeadingTrivia, SyntaxKind.LessThanToken, "{", "{", oldToken.TrailingTrivia); } if (oldToken.Kind() == SyntaxKind.GreaterThanToken) { - return SyntaxFactory.Token(oldToken.LeadingTrivia, SyntaxKind.GreaterThanToken, "}", "}", oldToken.TrailingTrivia); + return Token(oldToken.LeadingTrivia, SyntaxKind.GreaterThanToken, "}", "}", oldToken.TrailingTrivia); } Debug.Assert(false, "This method is used only replacing the '<' and '>' to '{' and '}' respectively"); @@ -1097,7 +1100,7 @@ private InvocationExpressionSyntax RewriteExtensionMethodInvocation( // We use .ParseExpression here, and not .GenerateTypeSyntax as we want this to be a property // MemberAccessExpression, and not a QualifiedNameSyntax. - var containingTypeSyntax = SyntaxFactory.ParseExpression(containingTypeString); + var containingTypeSyntax = ParseExpression(containingTypeString); var newContainingType = _semanticModel.GetSpeculativeSymbolInfo(speculationPosition, containingTypeSyntax, SpeculativeBindingOption.BindAsExpression).Symbol; if (newContainingType == null || !newContainingType.Equals(reducedExtensionMethod.ContainingType)) return originalNode; @@ -1109,13 +1112,13 @@ private InvocationExpressionSyntax RewriteExtensionMethodInvocation( // Copies the annotation for the member access expression newMemberAccess = originalNode.Expression.CopyAnnotationsTo(newMemberAccess).WithAdditionalAnnotations(Simplifier.Annotation); - var thisArgument = SyntaxFactory.Argument(thisExpression).WithLeadingTrivia(SyntaxTriviaList.Empty); + var thisArgument = Argument(thisExpression).WithLeadingTrivia(SyntaxTriviaList.Empty); // Copies the annotation for the left hand side of the member access expression to the first argument in the complexified form thisArgument = originalMemberAccess.Expression.CopyAnnotationsTo(thisArgument); var arguments = originalNode.ArgumentList.Arguments.Insert(0, thisArgument); - var replacementNode = SyntaxFactory.InvocationExpression( + var replacementNode = InvocationExpression( newMemberAccess, originalNode.ArgumentList.WithArguments(arguments)); diff --git a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs index 127cfd73e6a56..55bdb955984c6 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs @@ -31,7 +31,7 @@ public static ImmutableArray Compute(SyntaxNode root, Func< { var reduceNodeComputer = new NodesAndTokensToReduceComputer(isNodeOrTokenOutsideSimplifySpans); reduceNodeComputer.Visit(root); - return reduceNodeComputer._nodesAndTokensToReduce.ToImmutableArray(); + return [.. reduceNodeComputer._nodesAndTokensToReduce]; } private NodesAndTokensToReduceComputer(Func isNodeOrTokenOutsideSimplifySpans) diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs index d9aee0b2ac3eb..31b5cfc0da3ca 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs @@ -21,6 +21,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers; using Microsoft.CodeAnalysis.Rename.ConflictEngine; +using static SyntaxFactory; internal class NameSimplifier : AbstractCSharpSimplifier { @@ -85,7 +86,7 @@ public override bool TrySimplify( if (symbol.Kind == SymbolKind.Method && name.Kind() == SyntaxKind.GenericName) { var genericName = (GenericNameSyntax)name; - replacementNode = SyntaxFactory.IdentifierName(genericName.Identifier) + replacementNode = IdentifierName(genericName.Identifier) .WithLeadingTrivia(genericName.GetLeadingTrivia()) .WithTrailingTrivia(genericName.GetTrailingTrivia()); @@ -125,7 +126,7 @@ public override bool TrySimplify( text = declIdentifier.IsVerbatimIdentifier() ? declIdentifier.ToString()[1..] : declIdentifier.ToString(); } - var identifierToken = SyntaxFactory.Identifier( + var identifierToken = Identifier( name.GetLeadingTrivia(), SyntaxKind.IdentifierToken, text, @@ -133,7 +134,7 @@ public override bool TrySimplify( name.GetTrailingTrivia()); identifierToken = CSharpSimplificationService.TryEscapeIdentifierToken(identifierToken, name); - replacementNode = SyntaxFactory.IdentifierName(identifierToken); + replacementNode = IdentifierName(identifierToken); // Merge annotation to new syntax node var annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(RenameAnnotation.Kind); @@ -290,7 +291,7 @@ public override bool TrySimplify( return false; } - replacementNode = SyntaxFactory.NullableType(oldType) + replacementNode = NullableType(oldType) .WithLeadingTrivia(name.GetLeadingTrivia()) .WithTrailingTrivia(name.GetTrailingTrivia()); issueSpan = name.Span; @@ -379,7 +380,7 @@ private static bool TryReduceCrefColorColor( // A.B.C with C. In this case the parent of A.B.C is A.B.C.D which is a // QualifiedCrefSyntax - var qualifiedReplacement = SyntaxFactory.QualifiedCref(replacement, qualifiedCrefParent.Member); + var qualifiedReplacement = QualifiedCref(replacement, qualifiedCrefParent.Member); if (QualifiedCrefSimplifier.CanSimplifyWithReplacement(qualifiedCrefParent, semanticModel, qualifiedReplacement, cancellationToken)) return true; } @@ -390,7 +391,7 @@ private static bool TryReduceCrefColorColor( // A.B with B. In this case the parent of A.B is A.B.C which is a // QualifiedNameSyntax - var qualifiedReplacement = SyntaxFactory.QualifiedName(replacementName, qualifiedParent.Right); + var qualifiedReplacement = QualifiedName(replacementName, qualifiedParent.Right); return CanReplaceWithReducedName( qualifiedParent, qualifiedReplacement, semanticModel, cancellationToken); } @@ -510,12 +511,12 @@ private static bool TryReduceAttributeSuffix( // if this attribute name in source contained Unicode escaping, we will loose it now // because there is no easy way to determine the substring from identifier->ToString() - // which would be needed to pass to SyntaxFactory.Identifier + // which would be needed to pass to Identifier // The result is an unescaped Unicode character in source. // once we remove the Attribute suffix, we can't use an escaped identifier var newIdentifierToken = identifierToken.CopyAnnotationsTo( - SyntaxFactory.Identifier( + Identifier( identifierToken.LeadingTrivia, newAttributeName, identifierToken.TrailingTrivia)); @@ -523,12 +524,12 @@ private static bool TryReduceAttributeSuffix( switch (name) { case GenericNameSyntax generic: - replacementNode = SyntaxFactory.GenericName(newIdentifierToken, generic.TypeArgumentList) + replacementNode = GenericName(newIdentifierToken, generic.TypeArgumentList) .WithLeadingTrivia(name.GetLeadingTrivia()); break; default: - replacementNode = SyntaxFactory.IdentifierName(newIdentifierToken) + replacementNode = IdentifierName(newIdentifierToken) .WithLeadingTrivia(name.GetLeadingTrivia()); break; } @@ -664,7 +665,7 @@ private static bool IsAmbiguousCast(ExpressionSyntax expression, ExpressionSynta castExpression.Type == expression) { var newCastExpression = castExpression.ReplaceNode(castExpression.Type, simplifiedNode); - var reparsedCastExpression = SyntaxFactory.ParseExpression(newCastExpression.ToString()); + var reparsedCastExpression = ParseExpression(newCastExpression.ToString()); if (!reparsedCastExpression.IsKind(SyntaxKind.CastExpression)) { diff --git a/src/Workspaces/CSharpTest/CodeGeneration/AddAttributesTests.cs b/src/Workspaces/CSharpTest/CodeGeneration/AddAttributesTests.cs index 27fa2160a9024..2f6adf67efba5 100644 --- a/src/Workspaces/CSharpTest/CodeGeneration/AddAttributesTests.cs +++ b/src/Workspaces/CSharpTest/CodeGeneration/AddAttributesTests.cs @@ -15,6 +15,9 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGeneration { + using static CSharpSyntaxTokens; + using static SyntaxFactory; + [UseExportProvider] public class AddAttributesTests { @@ -38,12 +41,12 @@ private static async Task TestAsync(string initialText, string attributeAddedTex var doc = GetDocument(initialText); var attributeList = - SyntaxFactory.AttributeList( - [SyntaxFactory.Attribute( - SyntaxFactory.IdentifierName("System.Reflection.AssemblyVersion(\"1.0.0.0\")"))]) + AttributeList( + [Attribute( + IdentifierName("System.Reflection.AssemblyVersion(\"1.0.0.0\")"))]) .WithTarget( - SyntaxFactory.AttributeTargetSpecifier( - SyntaxFactory.Token(SyntaxKind.AssemblyKeyword))); + AttributeTargetSpecifier( + AssemblyKeyword)); var syntaxRoot = await doc.GetSyntaxRootAsync(); var editor = await DocumentEditor.CreateAsync(doc); diff --git a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs index d5e06181cc09c..15bdfdbda86cf 100644 --- a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs +++ b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; -using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Formatting; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; @@ -21,19 +20,18 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Editing { + using static CSharpSyntaxTokens; + using static SyntaxFactory; + [UseExportProvider] - public class SyntaxGeneratorTests + public sealed class SyntaxGeneratorTests { private readonly CSharpCompilation _emptyCompilation = CSharpCompilation.Create("empty", - references: new[] { TestMetadata.Net451.mscorlib, TestMetadata.Net451.System }); + references: [TestMetadata.Net451.mscorlib, TestMetadata.Net451.System]); private Workspace _workspace; private SyntaxGenerator _generator; - public SyntaxGeneratorTests() - { - } - private Workspace Workspace => _workspace ??= new AdhocWorkspace(); @@ -44,7 +42,7 @@ public static Compilation Compile(string code) { return CSharpCompilation.Create("test") .AddReferences(TestMetadata.Net451.mscorlib, TestMetadata.Net451.System, TestMetadata.Net451.SystemCore, TestMetadata.Net451.SystemRuntime, TestReferences.NetFx.ValueTuple.tuplelib) - .AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(code)); + .AddSyntaxTrees(ParseSyntaxTree(code)); } private static void VerifySyntax(SyntaxNode node, string expectedText) where TSyntax : SyntaxNode @@ -970,6 +968,20 @@ public void TestOperatorDeclaration() "explicit operator bool (global::System.Int32 p0, global::System.String p1)\r\n{\r\n}"); } + [Fact, WorkItem(63410, "https://github.com/dotnet/roslyn/issues/63410")] + public void TestCheckedOperator() + { + var comp = CSharpCompilation.Create(null, new[] { SyntaxFactory.ParseSyntaxTree(""" + public class C + { + public static C operator checked ++(C x) => x; + public static C operator ++(C x) => x; + } + """) }); + var operatorSymbol = (IMethodSymbol)comp.GetTypeByMetadataName("C").GetMembers(WellKnownMemberNames.CheckedIncrementOperatorName).Single(); + VerifySyntax(Generator.OperatorDeclaration(operatorSymbol), "public static global::C operator checked ++(global::C x)\r\n{\r\n}"); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/65833")] public void TestConversionOperatorDeclaration() { @@ -1188,266 +1200,266 @@ public void TestAsPublicInterfaceImplementation() #region ExpressionBodyTests VerifySyntax( Generator.AsPublicInterfaceImplementation( - SyntaxFactory.MethodDeclaration( - SyntaxFactory.PredefinedType( - SyntaxFactory.Token( + MethodDeclaration( + PredefinedType( + Token( [], SyntaxKind.ObjectKeyword, - SyntaxFactory.TriviaList( - SyntaxFactory.Space))), - SyntaxFactory.Identifier("DoSomething")) + TriviaList( + Space))), + Identifier("DoSomething")) .WithExplicitInterfaceSpecifier( - SyntaxFactory.ExplicitInterfaceSpecifier( - SyntaxFactory.IdentifierName("IGeneral"))) + ExplicitInterfaceSpecifier( + IdentifierName("IGeneral"))) .WithParameterList( - SyntaxFactory.ParameterList() + ParameterList() .WithCloseParenToken( - SyntaxFactory.Token( + Token( [], SyntaxKind.CloseParenToken, - SyntaxFactory.TriviaList( - SyntaxFactory.Space)))) + TriviaList( + Space)))) .WithExpressionBody( - SyntaxFactory.ArrowExpressionClause( - SyntaxFactory.ImplicitObjectCreationExpression()) + ArrowExpressionClause( + ImplicitObjectCreationExpression()) .WithArrowToken( - SyntaxFactory.Token( + Token( [], SyntaxKind.EqualsGreaterThanToken, - SyntaxFactory.TriviaList( - SyntaxFactory.Space)))) + TriviaList( + Space)))) .WithSemicolonToken( - SyntaxFactory.Token(SyntaxKind.SemicolonToken)), + SemicolonToken), Generator.IdentifierName("i")), "public object DoSomething() => new();"); VerifySyntax( Generator.AsPublicInterfaceImplementation( - SyntaxFactory.OperatorDeclaration( - SyntaxFactory.PredefinedType( - SyntaxFactory.Token(SyntaxKind.IntKeyword)), - SyntaxFactory.Token(SyntaxKind.PlusToken)) + OperatorDeclaration( + PredefinedType( + IntKeyword), + PlusToken) .WithModifiers([ - SyntaxFactory.Token(SyntaxKind.PublicKeyword), - SyntaxFactory.Token(SyntaxKind.StaticKeyword)]) + PublicKeyword, + StaticKeyword]) .WithExplicitInterfaceSpecifier( - SyntaxFactory.ExplicitInterfaceSpecifier( - SyntaxFactory.GenericName( - SyntaxFactory.Identifier("IGeneral")) + ExplicitInterfaceSpecifier( + GenericName( + Identifier("IGeneral")) .WithTypeArgumentList( - SyntaxFactory.TypeArgumentList([SyntaxFactory.IdentifierName("C")])))) + TypeArgumentList([IdentifierName("C")])))) .WithParameterList( - SyntaxFactory.ParameterList( - SyntaxFactory.SeparatedList( + ParameterList( + SeparatedList( new SyntaxNodeOrToken[]{ - SyntaxFactory.Parameter( - SyntaxFactory.Identifier("x")) + Parameter( + Identifier("x")) .WithType( - SyntaxFactory.IdentifierName("C")), - SyntaxFactory.Token(SyntaxKind.CommaToken), - SyntaxFactory.Parameter( - SyntaxFactory.Identifier("y")) + IdentifierName("C")), + CommaToken, + Parameter( + Identifier("y")) .WithType( - SyntaxFactory.IdentifierName("C"))}))) + IdentifierName("C"))}))) .WithExpressionBody( - SyntaxFactory.ArrowExpressionClause( - SyntaxFactory.LiteralExpression( + ArrowExpressionClause( + LiteralExpression( SyntaxKind.NumericLiteralExpression, - SyntaxFactory.Literal(0)))) + Literal(0)))) .WithSemicolonToken( - SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + SemicolonToken) .NormalizeWhitespace(), Generator.IdentifierName("i")), "public static int operator +(C x, C y) => 0;"); VerifySyntax( Generator.AsPublicInterfaceImplementation( - SyntaxFactory.ConversionOperatorDeclaration( - SyntaxFactory.Token( + ConversionOperatorDeclaration( + Token( [], SyntaxKind.ImplicitKeyword, - SyntaxFactory.TriviaList( - SyntaxFactory.Space)), - SyntaxFactory.PredefinedType( - SyntaxFactory.Token(SyntaxKind.StringKeyword))) + TriviaList( + Space)), + PredefinedType( + StringKeyword)) .WithModifiers( - [SyntaxFactory.Token( + [Token( [], SyntaxKind.StaticKeyword, - SyntaxFactory.TriviaList( - SyntaxFactory.Space))]) + TriviaList( + Space))]) .WithExplicitInterfaceSpecifier( - SyntaxFactory.ExplicitInterfaceSpecifier( - SyntaxFactory.GenericName( - SyntaxFactory.Identifier("IGeneral")) + ExplicitInterfaceSpecifier( + GenericName( + Identifier("IGeneral")) .WithTypeArgumentList( - SyntaxFactory.TypeArgumentList([SyntaxFactory.IdentifierName("C")])))) + TypeArgumentList([IdentifierName("C")])))) .WithOperatorKeyword( - SyntaxFactory.Token( + Token( [], SyntaxKind.OperatorKeyword, - SyntaxFactory.TriviaList( - SyntaxFactory.Space))) + TriviaList( + Space))) .WithParameterList( - SyntaxFactory.ParameterList( - [SyntaxFactory.Parameter( - SyntaxFactory.Identifier("x")) + ParameterList( + [Parameter( + Identifier("x")) .WithType( - SyntaxFactory.IdentifierName( - SyntaxFactory.Identifier( + IdentifierName( + Identifier( [], "C", - SyntaxFactory.TriviaList( - SyntaxFactory.Space))))]) + TriviaList( + Space))))]) .WithCloseParenToken( - SyntaxFactory.Token( + Token( [], SyntaxKind.CloseParenToken, - SyntaxFactory.TriviaList( - SyntaxFactory.Space)))) + TriviaList( + Space)))) .WithExpressionBody( - SyntaxFactory.ArrowExpressionClause( - SyntaxFactory.LiteralExpression( + ArrowExpressionClause( + LiteralExpression( SyntaxKind.NullLiteralExpression)) .WithArrowToken( - SyntaxFactory.Token( + Token( [], SyntaxKind.EqualsGreaterThanToken, - SyntaxFactory.TriviaList( - SyntaxFactory.Space)))) + TriviaList( + Space)))) .WithSemicolonToken( - SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + SemicolonToken) .NormalizeWhitespace(), Generator.IdentifierName("i")), "public static implicit operator string (C x) => null;"); VerifySyntax( Generator.AsPublicInterfaceImplementation( - SyntaxFactory.PropertyDeclaration( - SyntaxFactory.PredefinedType( - SyntaxFactory.Token(SyntaxKind.IntKeyword)), - SyntaxFactory.Identifier("Num")) + PropertyDeclaration( + PredefinedType( + IntKeyword), + Identifier("Num")) .WithExplicitInterfaceSpecifier( - SyntaxFactory.ExplicitInterfaceSpecifier( - SyntaxFactory.IdentifierName("IGeneral"))) + ExplicitInterfaceSpecifier( + IdentifierName("IGeneral"))) .WithExpressionBody( - SyntaxFactory.ArrowExpressionClause( - SyntaxFactory.LiteralExpression( + ArrowExpressionClause( + LiteralExpression( SyntaxKind.NumericLiteralExpression, - SyntaxFactory.Literal(0)))) + Literal(0)))) .WithSemicolonToken( - SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + SemicolonToken) .NormalizeWhitespace(), Generator.IdentifierName("i")), "public int Num => 0;"); VerifySyntax( Generator.AsPublicInterfaceImplementation( - SyntaxFactory.PropertyDeclaration( - SyntaxFactory.PredefinedType( - SyntaxFactory.Token(SyntaxKind.IntKeyword)), - SyntaxFactory.Identifier("Num")) + PropertyDeclaration( + PredefinedType( + IntKeyword), + Identifier("Num")) .WithExplicitInterfaceSpecifier( - SyntaxFactory.ExplicitInterfaceSpecifier( - SyntaxFactory.IdentifierName("IGeneral"))) + ExplicitInterfaceSpecifier( + IdentifierName("IGeneral"))) .WithAccessorList( - SyntaxFactory.AccessorList([ - SyntaxFactory.AccessorDeclaration( + AccessorList([ + AccessorDeclaration( SyntaxKind.GetAccessorDeclaration) .WithExpressionBody( - SyntaxFactory.ArrowExpressionClause( - SyntaxFactory.LiteralExpression( + ArrowExpressionClause( + LiteralExpression( SyntaxKind.NumericLiteralExpression, - SyntaxFactory.Literal(0)))) + Literal(0)))) .WithSemicolonToken( - SyntaxFactory.Token(SyntaxKind.SemicolonToken))])) + SemicolonToken)])) .NormalizeWhitespace(), Generator.IdentifierName("i")), "public int Num { get => 0; }"); VerifySyntax( Generator.AsPublicInterfaceImplementation( - SyntaxFactory.IndexerDeclaration( - SyntaxFactory.PredefinedType( - SyntaxFactory.Token(SyntaxKind.IntKeyword))) + IndexerDeclaration( + PredefinedType( + IntKeyword)) .WithExplicitInterfaceSpecifier( - SyntaxFactory.ExplicitInterfaceSpecifier( - SyntaxFactory.IdentifierName("IGeneral"))) + ExplicitInterfaceSpecifier( + IdentifierName("IGeneral"))) .WithParameterList( - SyntaxFactory.BracketedParameterList( - [SyntaxFactory.Parameter( - SyntaxFactory.Identifier("index")) + BracketedParameterList( + [Parameter( + Identifier("index")) .WithType( - SyntaxFactory.PredefinedType( - SyntaxFactory.Token(SyntaxKind.IntKeyword)))])) + PredefinedType( + IntKeyword))])) .WithExpressionBody( - SyntaxFactory.ArrowExpressionClause( - SyntaxFactory.LiteralExpression( + ArrowExpressionClause( + LiteralExpression( SyntaxKind.NumericLiteralExpression, - SyntaxFactory.Literal(0)))) + Literal(0)))) .WithSemicolonToken( - SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + SemicolonToken) .NormalizeWhitespace(), Generator.IdentifierName("i")), "public int this[int index] => 0;"); VerifySyntax( Generator.AsPublicInterfaceImplementation( - SyntaxFactory.IndexerDeclaration( - SyntaxFactory.PredefinedType( - SyntaxFactory.Token(SyntaxKind.IntKeyword))) + IndexerDeclaration( + PredefinedType( + IntKeyword)) .WithExplicitInterfaceSpecifier( - SyntaxFactory.ExplicitInterfaceSpecifier( - SyntaxFactory.IdentifierName("IGeneral"))) + ExplicitInterfaceSpecifier( + IdentifierName("IGeneral"))) .WithParameterList( - SyntaxFactory.BracketedParameterList( - [SyntaxFactory.Parameter( - SyntaxFactory.Identifier("index")) + BracketedParameterList( + [Parameter( + Identifier("index")) .WithType( - SyntaxFactory.PredefinedType( - SyntaxFactory.Token(SyntaxKind.IntKeyword)))])) + PredefinedType( + IntKeyword))])) .WithAccessorList( - SyntaxFactory.AccessorList( - SyntaxFactory.SingletonList( - SyntaxFactory.AccessorDeclaration( + AccessorList( + SingletonList( + AccessorDeclaration( SyntaxKind.GetAccessorDeclaration) .WithExpressionBody( - SyntaxFactory.ArrowExpressionClause( - SyntaxFactory.LiteralExpression( + ArrowExpressionClause( + LiteralExpression( SyntaxKind.NumericLiteralExpression, - SyntaxFactory.Literal(0)))) + Literal(0)))) .WithSemicolonToken( - SyntaxFactory.Token(SyntaxKind.SemicolonToken))))) + SemicolonToken)))) .NormalizeWhitespace(), Generator.IdentifierName("i")), "public int this[int index] { get => 0; }"); VerifySyntax( Generator.AsPublicInterfaceImplementation( - SyntaxFactory.EventDeclaration( - SyntaxFactory.IdentifierName("EventHandler"), - SyntaxFactory.Identifier("Event")) + EventDeclaration( + IdentifierName("EventHandler"), + Identifier("Event")) .WithExplicitInterfaceSpecifier( - SyntaxFactory.ExplicitInterfaceSpecifier( - SyntaxFactory.IdentifierName("IGeneral"))) + ExplicitInterfaceSpecifier( + IdentifierName("IGeneral"))) .WithAccessorList( - SyntaxFactory.AccessorList([ - SyntaxFactory.AccessorDeclaration( + AccessorList([ + AccessorDeclaration( SyntaxKind.AddAccessorDeclaration) .WithExpressionBody( - SyntaxFactory.ArrowExpressionClause( - SyntaxFactory.LiteralExpression( + ArrowExpressionClause( + LiteralExpression( SyntaxKind.NullLiteralExpression))) .WithSemicolonToken( - SyntaxFactory.Token(SyntaxKind.SemicolonToken)), - SyntaxFactory.AccessorDeclaration( + SemicolonToken), + AccessorDeclaration( SyntaxKind.RemoveAccessorDeclaration) .WithExpressionBody( - SyntaxFactory.ArrowExpressionClause( - SyntaxFactory.LiteralExpression( + ArrowExpressionClause( + LiteralExpression( SyntaxKind.NullLiteralExpression))) .WithSemicolonToken( - SyntaxFactory.Token(SyntaxKind.SemicolonToken))])) + SemicolonToken)])) .NormalizeWhitespace(), Generator.IdentifierName("i")), "public event EventHandler Event { add => null; remove => null; }"); @@ -1504,7 +1516,7 @@ public interface IFace void Method() where T : class; }"; - var cu = SyntaxFactory.ParseCompilationUnit(code); + var cu = ParseCompilationUnit(code); var iface = cu.Members[0]; var method = Generator.GetMembers(iface)[0]; @@ -1923,19 +1935,19 @@ public void TestAddAttributes() VerifySyntax( Generator.AddAttributes( - SyntaxFactory.BreakStatement(), + BreakStatement(), Generator.Attribute("a")), "[a]\r\nbreak;"); VerifySyntax( Generator.AddAttributes( - SyntaxFactory.TypeParameter("T"), + TypeParameter("T"), Generator.Attribute("a")), "[a]\r\nT"); VerifySyntax( Generator.AddAttributes( - SyntaxFactory.ParenthesizedLambdaExpression(), + ParenthesizedLambdaExpression(), Generator.Attribute("a")), "[a]\r\n() =>"); } @@ -1968,7 +1980,7 @@ private void CheckAddRemoveAttribute(SyntaxNode declaration) [Fact] public void TestAddRemoveAttributesPerservesTrivia() { - var cls = SyntaxFactory.ParseCompilationUnit(@"// comment + var cls = ParseCompilationUnit(@"// comment public class C { } // end").Members[0]; var added = Generator.AddAttributes(cls, Generator.Attribute("a")); @@ -2190,7 +2202,7 @@ public void TestEnumWithUnderlyingTypeFromSymbol() [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66381")] public void TestDelegateDeclarationFromSymbol() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" public delegate void D(); """)); @@ -2204,7 +2216,7 @@ public void TestDelegateDeclarationFromSymbol() [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/65638")] public void TestMethodDeclarationFromSymbol1() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" class C { void M(int i = int.MaxValue) { } @@ -2225,7 +2237,7 @@ private void M(global::System.Int32 i = global::System.Int32.MaxValue) [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/65835")] public void TestMethodDeclarationFromSymbol2() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" class C { void M(params int[] arr) { } @@ -2246,7 +2258,7 @@ private void M(params global::System.Int32[] arr) [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/65835")] public void TestMethodDeclarationFromSymbol3() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" static class C { static void M(this int i) { } @@ -2267,7 +2279,7 @@ private static void M(this global::System.Int32 i) [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/65835")] public void TestMethodDeclarationFromSymbol4() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" static class C { static void M(this ref int i) { } @@ -2288,7 +2300,7 @@ private static void M(this ref global::System.Int32 i) [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/65638")] public void TestConstructorDeclarationFromSymbol1() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" class C { public C(int i = int.MaxValue) { } @@ -2310,7 +2322,7 @@ public C(global::System.Int32 i = global::System.Int32.MaxValue) [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66379")] public void TestPropertyDeclarationFromSymbol1() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" class C { public int Prop { get; protected set; } @@ -2328,7 +2340,7 @@ class C [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66379")] public void TestPropertyDeclarationFromSymbol2() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" class C { public int Prop { protected get; set; } @@ -2346,7 +2358,7 @@ class C [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66382")] public void TestOverrideDefaultConstraint1() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" public abstract partial class A { public abstract TResult? Accept(int a); @@ -2374,7 +2386,7 @@ public sealed partial class B : A [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66382")] public void TestOverrideDefaultConstraint2() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" public abstract partial class A { public abstract TResult? Accept(int a) where TResult : class; @@ -2402,7 +2414,7 @@ public sealed partial class B : A [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66382")] public void TestOverrideDefaultConstraint3() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" public abstract partial class A { public abstract TResult? Accept(int a) where TResult : struct; @@ -2429,7 +2441,7 @@ public sealed partial class B : A [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66382")] public void TestOverrideDefaultConstraint4() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" public class X { } @@ -2461,7 +2473,7 @@ public sealed partial class B : A [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66375")] public void TestExplicitInterface1() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" public interface IGoo { void BarMethod(); @@ -2515,7 +2527,7 @@ event System.Action IGoo.E { add { } remove { } } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66380")] public void TestConstantFieldDeclarations() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" class C { public const int F; @@ -2535,7 +2547,7 @@ public void TestConstantDecimalFieldDeclarationFromMetadata() { var compilation = _emptyCompilation. WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)). - AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + AddSyntaxTrees(ParseSyntaxTree(""" class C { public const decimal F = 8675309000000M; @@ -2566,7 +2578,7 @@ public void TestConstantFieldDeclarationSpecialTypes() [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66374")] public void TestDestructor1() { - var compilation = _emptyCompilation.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(""" + var compilation = _emptyCompilation.AddSyntaxTrees(ParseSyntaxTree(""" class C { ~C() @@ -2645,7 +2657,7 @@ public class C { }"; - var cu = SyntaxFactory.ParseCompilationUnit(code); + var cu = ParseCompilationUnit(code); var cls = cu.Members[0]; var summary = cls.DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -2669,7 +2681,7 @@ public class C { }"; - var cu = SyntaxFactory.ParseCompilationUnit(code); + var cu = ParseCompilationUnit(code); var cls = cu.Members[0]; var summary = cls.DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -2694,7 +2706,7 @@ public class C { }"; - var cu = SyntaxFactory.ParseCompilationUnit(code); + var cu = ParseCompilationUnit(code); var cls = cu.Members[0]; var text = cls.DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -2717,7 +2729,7 @@ public class C { }"; - var cu = SyntaxFactory.ParseCompilationUnit(code); + var cu = ParseCompilationUnit(code); var cls = cu.Members[0]; var text = cls.DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -2781,6 +2793,45 @@ interface i """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/65932")] + public void TestAddExpressionBodyMembersToInterface() + { + var method = (MethodDeclarationSyntax)Generator.MethodDeclaration("m"); + method = method.WithBody(null).WithSemicolonToken(SemicolonToken); + method = method.WithExpressionBody(ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); + + VerifySyntax(Generator.AddMembers(Generator.InterfaceDeclaration("i"), + [method]), + """ + interface i + { + void m(); + } + """); + + var getAccessor = (AccessorDeclarationSyntax)Generator.GetAccessorDeclaration(); + getAccessor = getAccessor.WithBody(null).WithSemicolonToken(SemicolonToken); + getAccessor = getAccessor.WithExpressionBody(ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); + + var setAccessor = (AccessorDeclarationSyntax)Generator.SetAccessorDeclaration(); + setAccessor = setAccessor.WithBody(null).WithSemicolonToken(SemicolonToken); + setAccessor = setAccessor.WithExpressionBody(ArrowExpressionClause((ExpressionSyntax)Generator.InvocationExpression(Generator.IdentifierName("x")))); + + var property = (PropertyDeclarationSyntax) + Generator.WithAccessorDeclarations( + Generator.PropertyDeclaration("p", Generator.IdentifierName("x")), + [getAccessor, setAccessor]); + + VerifySyntax(Generator.AddMembers(Generator.InterfaceDeclaration("i"), + [property]), + """ + interface i + { + x p { get; set; } + } + """); + } + [Fact] public void TestRemoveMembers() { @@ -2907,7 +2958,7 @@ public void TestGetAccessibility() Assert.Equal(Accessibility.NotApplicable, Generator.GetAccessibility(Generator.NamespaceImportDeclaration("u"))); Assert.Equal(Accessibility.NotApplicable, Generator.GetAccessibility(Generator.LocalDeclarationStatement(Generator.IdentifierName("t"), "loc"))); Assert.Equal(Accessibility.NotApplicable, Generator.GetAccessibility(Generator.Attribute("a"))); - Assert.Equal(Accessibility.NotApplicable, Generator.GetAccessibility(SyntaxFactory.TypeParameter("tp"))); + Assert.Equal(Accessibility.NotApplicable, Generator.GetAccessibility(TypeParameter("tp"))); } [Fact] @@ -2931,8 +2982,8 @@ public void TestWithAccessibility() Assert.Equal(Accessibility.NotApplicable, Generator.GetAccessibility(Generator.WithAccessibility(Generator.NamespaceImportDeclaration("u"), Accessibility.Private))); Assert.Equal(Accessibility.NotApplicable, Generator.GetAccessibility(Generator.WithAccessibility(Generator.LocalDeclarationStatement(Generator.IdentifierName("t"), "loc"), Accessibility.Private))); Assert.Equal(Accessibility.NotApplicable, Generator.GetAccessibility(Generator.WithAccessibility(Generator.Attribute("a"), Accessibility.Private))); - Assert.Equal(Accessibility.NotApplicable, Generator.GetAccessibility(Generator.WithAccessibility(SyntaxFactory.TypeParameter("tp"), Accessibility.Private))); - Assert.Equal(Accessibility.Private, Generator.GetAccessibility(Generator.WithAccessibility(SyntaxFactory.AccessorDeclaration(SyntaxKind.InitAccessorDeclaration), Accessibility.Private))); + Assert.Equal(Accessibility.NotApplicable, Generator.GetAccessibility(Generator.WithAccessibility(TypeParameter("tp"), Accessibility.Private))); + Assert.Equal(Accessibility.Private, Generator.GetAccessibility(Generator.WithAccessibility(AccessorDeclaration(SyntaxKind.InitAccessorDeclaration), Accessibility.Private))); } [Fact] @@ -2955,7 +3006,7 @@ public void TestGetModifiers() Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(Generator.NamespaceImportDeclaration("u"))); Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(Generator.LocalDeclarationStatement(Generator.IdentifierName("t"), "loc"))); Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(Generator.Attribute("a"))); - Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(SyntaxFactory.TypeParameter("tp"))); + Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(TypeParameter("tp"))); } [Fact] @@ -2978,7 +3029,7 @@ public void TestWithModifiers() Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(Generator.WithModifiers(Generator.NamespaceImportDeclaration("u"), DeclarationModifiers.Abstract))); Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(Generator.WithModifiers(Generator.LocalDeclarationStatement(Generator.IdentifierName("t"), "loc"), DeclarationModifiers.Abstract))); Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(Generator.WithModifiers(Generator.Attribute("a"), DeclarationModifiers.Abstract))); - Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(Generator.WithModifiers(SyntaxFactory.TypeParameter("tp"), DeclarationModifiers.Abstract))); + Assert.Equal(DeclarationModifiers.None, Generator.GetModifiers(Generator.WithModifiers(TypeParameter("tp"), DeclarationModifiers.Abstract))); } [Fact] @@ -3016,7 +3067,7 @@ public void TestWithModifiers_AllowedModifiers() Assert.Equal( DeclarationModifiers.Unsafe, - Generator.GetModifiers(Generator.WithModifiers(SyntaxFactory.DestructorDeclaration("c"), allModifiers))); + Generator.GetModifiers(Generator.WithModifiers(DestructorDeclaration("c"), allModifiers))); Assert.Equal( DeclarationModifiers.Abstract | DeclarationModifiers.Async | DeclarationModifiers.New | DeclarationModifiers.Override | DeclarationModifiers.Partial | DeclarationModifiers.Sealed | DeclarationModifiers.Static | DeclarationModifiers.Virtual | DeclarationModifiers.Unsafe | DeclarationModifiers.ReadOnly, @@ -3040,7 +3091,7 @@ public void TestWithModifiers_AllowedModifiers() Assert.Equal( DeclarationModifiers.Abstract | DeclarationModifiers.New | DeclarationModifiers.Override | DeclarationModifiers.Virtual | DeclarationModifiers.ReadOnly, - Generator.GetModifiers(Generator.WithModifiers(SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration), allModifiers))); + Generator.GetModifiers(Generator.WithModifiers(AccessorDeclaration(SyntaxKind.GetAccessorDeclaration), allModifiers))); } [Fact] @@ -3074,7 +3125,7 @@ public void TestAddStaticToPublicConstructor() [Fact] public void TestAddAbstractToFileClass() { - var fileClass = (ClassDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration("file class C { }"); + var fileClass = (ClassDeclarationSyntax)ParseMemberDeclaration("file class C { }"); var fileAbstractClass = Generator.WithModifiers(fileClass, Generator.GetModifiers(fileClass).WithIsAbstract(true)); VerifySyntax(fileAbstractClass, @"file abstract class C { @@ -3084,7 +3135,7 @@ public void TestAddAbstractToFileClass() [Fact] public void TestAddPublicToFileClass() { - var fileClass = (ClassDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration("file class C { }"); + var fileClass = (ClassDeclarationSyntax)ParseMemberDeclaration("file class C { }"); var filePublicClass = Generator.WithAccessibility(fileClass, Accessibility.Public); VerifySyntax(filePublicClass, @"public class C { @@ -3094,7 +3145,7 @@ public void TestAddPublicToFileClass() [Fact] public void TestAddFileModifierToAbstractClass() { - var abstractClass = (ClassDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration("abstract class C { }"); + var abstractClass = (ClassDeclarationSyntax)ParseMemberDeclaration("abstract class C { }"); var fileAbstractClass = Generator.WithModifiers(abstractClass, Generator.GetModifiers(abstractClass).WithIsFile(true)); VerifySyntax(fileAbstractClass, @"file abstract class C { @@ -3104,7 +3155,7 @@ public void TestAddFileModifierToAbstractClass() [Fact] public void TestAddFileModifierToPublicClass() { - var publicClass = (ClassDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration("public class C { }"); + var publicClass = (ClassDeclarationSyntax)ParseMemberDeclaration("public class C { }"); var filePublicClass = Generator.WithModifiers(publicClass, Generator.GetModifiers(publicClass).WithIsFile(true)); VerifySyntax(filePublicClass, @"file class C { @@ -3114,7 +3165,7 @@ public void TestAddFileModifierToPublicClass() [Fact] public void TestAddRequiredModifierToVirtualProperty() { - var property = (PropertyDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration("public virtual int P { get; }"); + var property = (PropertyDeclarationSyntax)ParseMemberDeclaration("public virtual int P { get; }"); var updatedProperty = Generator.WithModifiers(property, Generator.GetModifiers(property).WithIsRequired(true)); VerifySyntax(updatedProperty, "public virtual required int P { get; }"); } @@ -3122,7 +3173,7 @@ public void TestAddRequiredModifierToVirtualProperty() [Fact] public void TestAddVirtualModifierToRequiredProperty() { - var property = (PropertyDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration("public required int P { get; }"); + var property = (PropertyDeclarationSyntax)ParseMemberDeclaration("public required int P { get; }"); var updatedProperty = Generator.WithModifiers(property, Generator.GetModifiers(property).WithIsVirtual(true)); VerifySyntax(updatedProperty, "public virtual required int P { get; }"); } @@ -3132,7 +3183,7 @@ public void TestAddVirtualModifierToRequiredProperty() [InlineData("protected internal")] public void TestCompoundAccessibilityModifierKeywordsOrder(string modifier) { - var property = (PropertyDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration($$"""{{modifier}} int P { get; }"""); + var property = (PropertyDeclarationSyntax)ParseMemberDeclaration($$"""{{modifier}} int P { get; }"""); var updatedProperty = Generator.WithModifiers(property, Generator.GetModifiers(property).WithIsRequired(true)); VerifySyntax(updatedProperty, $$"""{{modifier}} required int P { get; }"""); } @@ -3245,15 +3296,15 @@ public void TestGetExpression() // expression bodied methods var method = (MethodDeclarationSyntax)Generator.MethodDeclaration("p"); - method = method.WithBody(null).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - method = method.WithExpressionBody(SyntaxFactory.ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); + method = method.WithBody(null).WithSemicolonToken(SemicolonToken); + method = method.WithExpressionBody(ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); Assert.Equal("x", Generator.GetExpression(method).ToString()); // expression bodied local functions - var local = SyntaxFactory.LocalFunctionStatement(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "p"); - local = local.WithBody(null).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - local = local.WithExpressionBody(SyntaxFactory.ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); + var local = LocalFunctionStatement(PredefinedType(VoidKeyword), "p"); + local = local.WithBody(null).WithSemicolonToken(SemicolonToken); + local = local.WithExpressionBody(ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); Assert.Equal("x", Generator.GetExpression(local).ToString()); } @@ -3281,15 +3332,15 @@ public void TestWithExpression() // expression bodied methods var method = (MethodDeclarationSyntax)Generator.MethodDeclaration("p"); - method = method.WithBody(null).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - method = method.WithExpressionBody(SyntaxFactory.ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); + method = method.WithBody(null).WithSemicolonToken(SemicolonToken); + method = method.WithExpressionBody(ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); Assert.Equal("y", Generator.GetExpression(Generator.WithExpression(method, Generator.IdentifierName("y"))).ToString()); // expression bodied local functions - var local = SyntaxFactory.LocalFunctionStatement(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "p"); - local = local.WithBody(null).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - local = local.WithExpressionBody(SyntaxFactory.ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); + var local = LocalFunctionStatement(PredefinedType(VoidKeyword), "p"); + local = local.WithBody(null).WithSemicolonToken(SemicolonToken); + local = local.WithExpressionBody(ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); Assert.Equal("y", Generator.GetExpression(Generator.WithExpression(local, Generator.IdentifierName("y"))).ToString()); } @@ -3389,7 +3440,7 @@ public void TestAccessorDeclarations2() [Fact] public void TestAccessorsOnSpecialProperties() { - var root = SyntaxFactory.ParseCompilationUnit( + var root = ParseCompilationUnit( @"class C { public int X { get; set; } = 100; @@ -3410,7 +3461,7 @@ public void TestAccessorsOnSpecialProperties() [Fact] public void TestAccessorsOnSpecialIndexers() { - var root = SyntaxFactory.ParseCompilationUnit( + var root = ParseCompilationUnit( @"class C { public int this[int p] { get { return p * 10; } set { } }; @@ -3432,7 +3483,7 @@ public void TestAccessorsOnSpecialIndexers() public void TestExpressionsOnSpecialProperties() { // you can get/set expression from both expression value property and initialized properties - var root = SyntaxFactory.ParseCompilationUnit( + var root = ParseCompilationUnit( @"class C { public int X { get; set; } = 100; @@ -3458,7 +3509,7 @@ public void TestExpressionsOnSpecialProperties() public void TestExpressionsOnSpecialIndexers() { // you can get/set expression from both expression value property and initialized properties - var root = SyntaxFactory.ParseCompilationUnit( + var root = ParseCompilationUnit( @"class C { public int this[int p] { get { return p * 10; } set { } }; @@ -3583,7 +3634,7 @@ public void TestWithAccessorStatements() [Fact] public void TestGetBaseAndInterfaceTypes() { - var classBI = SyntaxFactory.ParseCompilationUnit( + var classBI = ParseCompilationUnit( @"class C : B, I { }").Members[0]; @@ -3594,7 +3645,7 @@ public void TestGetBaseAndInterfaceTypes() Assert.Equal("B", baseListBI[0].ToString()); Assert.Equal("I", baseListBI[1].ToString()); - var classB = SyntaxFactory.ParseCompilationUnit( + var classB = ParseCompilationUnit( @"class C : B { }").Members[0]; @@ -3604,7 +3655,7 @@ public void TestGetBaseAndInterfaceTypes() Assert.Equal(1, baseListB.Count); Assert.Equal("B", baseListB[0].ToString()); - var classN = SyntaxFactory.ParseCompilationUnit( + var classN = ParseCompilationUnit( @"class C { }").Members[0]; @@ -3617,7 +3668,7 @@ public void TestGetBaseAndInterfaceTypes() [Fact] public void TestRemoveBaseAndInterfaceTypes() { - var classBI = SyntaxFactory.ParseCompilationUnit( + var classBI = ParseCompilationUnit( @"class C : B, I { }").Members[0]; @@ -3647,17 +3698,17 @@ public void TestRemoveBaseAndInterfaceTypes() [Fact] public void TestAddBaseType() { - var classC = SyntaxFactory.ParseCompilationUnit( + var classC = ParseCompilationUnit( @"class C { }").Members[0]; - var classCI = SyntaxFactory.ParseCompilationUnit( + var classCI = ParseCompilationUnit( @"class C : I { }").Members[0]; - var classCB = SyntaxFactory.ParseCompilationUnit( + var classCB = ParseCompilationUnit( @"class C : B { }").Members[0]; @@ -3685,17 +3736,17 @@ public void TestAddBaseType() [Fact] public void TestAddInterfaceTypes() { - var classC = SyntaxFactory.ParseCompilationUnit( + var classC = ParseCompilationUnit( @"class C { }").Members[0]; - var classCI = SyntaxFactory.ParseCompilationUnit( + var classCI = ParseCompilationUnit( @"class C : I { }").Members[0]; - var classCB = SyntaxFactory.ParseCompilationUnit( + var classCB = ParseCompilationUnit( @"class C : B { }").Members[0]; @@ -3886,7 +3937,7 @@ public void TestMultiFieldDeclarations() public static int Q, Y, Z; }"); VerifySyntax( - Generator.ReplaceNode(declC, declX.GetAncestorOrThis(), SyntaxFactory.VariableDeclarator("Q")), + Generator.ReplaceNode(declC, declX.GetAncestorOrThis(), VariableDeclarator("Q")), @"public class C { public static int Q, Y, Z; @@ -3946,7 +3997,7 @@ public void TestInsertMembersOnRecordStruct_SemiColon() "; var comp = CSharpCompilation.Create("test") .AddReferences(TestMetadata.Net451.mscorlib) - .AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(src, options: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview))); + .AddSyntaxTrees(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()); @@ -4526,7 +4577,7 @@ public class C : IDisposable } "; - var root = SyntaxFactory.ParseCompilationUnit(text); + var root = ParseCompilationUnit(text); var decl = root.DescendantNodes().OfType().First(); var newDecl = Generator.AddInterfaceType(decl, Generator.IdentifierName("IDisposable")); var newRoot = root.ReplaceNode(decl, newDecl); diff --git a/src/Workspaces/CSharpTest/EmbeddedLanguages/VirtualChars/CSharpVirtualCharServiceTests.cs b/src/Workspaces/CSharpTest/EmbeddedLanguages/VirtualChars/CSharpVirtualCharServiceTests.cs index 14658ef41a415..997cc864868ec 100644 --- a/src/Workspaces/CSharpTest/EmbeddedLanguages/VirtualChars/CSharpVirtualCharServiceTests.cs +++ b/src/Workspaces/CSharpTest/EmbeddedLanguages/VirtualChars/CSharpVirtualCharServiceTests.cs @@ -29,7 +29,7 @@ public class CSharpVirtualCharServiceTests if (expression is LiteralExpressionSyntax literal) { - return SpecializedCollections.SingletonEnumerable(literal.Token); + return [literal.Token]; } else if (expression is InterpolatedStringExpressionSyntax interpolation) { diff --git a/src/Workspaces/CSharpTest/Formatting/CSharpFormattingTestBase.cs b/src/Workspaces/CSharpTest/Formatting/CSharpFormattingTestBase.cs index 26639254771cd..80a275a4a52f7 100644 --- a/src/Workspaces/CSharpTest/Formatting/CSharpFormattingTestBase.cs +++ b/src/Workspaces/CSharpTest/Formatting/CSharpFormattingTestBase.cs @@ -30,7 +30,7 @@ private protected Task AssertNoFormattingChangesAsync( bool testWithTransformation = true, ParseOptions parseOptions = null) { - return AssertFormatAsync(code, code, SpecializedCollections.SingletonEnumerable(new TextSpan(0, code.Length)), debugMode, changedOptionSet, testWithTransformation, parseOptions); + return AssertFormatAsync(code, code, [new TextSpan(0, code.Length)], debugMode, changedOptionSet, testWithTransformation, parseOptions); } private protected Task AssertFormatAsync( @@ -41,7 +41,7 @@ private protected Task AssertFormatAsync( bool testWithTransformation = true, ParseOptions parseOptions = null) { - return AssertFormatAsync(expected, code, SpecializedCollections.SingletonEnumerable(new TextSpan(0, code.Length)), debugMode, changedOptionSet, testWithTransformation, parseOptions); + return AssertFormatAsync(expected, code, [new TextSpan(0, code.Length)], debugMode, changedOptionSet, testWithTransformation, parseOptions); } private protected Task AssertFormatAsync( diff --git a/src/Workspaces/CSharpTest/Formatting/FormattingElasticTriviaTests.cs b/src/Workspaces/CSharpTest/Formatting/FormattingElasticTriviaTests.cs index 05071d233c0b9..5d560b8205611 100644 --- a/src/Workspaces/CSharpTest/Formatting/FormattingElasticTriviaTests.cs +++ b/src/Workspaces/CSharpTest/Formatting/FormattingElasticTriviaTests.cs @@ -16,6 +16,9 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Formatting { + using static CSharpSyntaxTokens; + using static SyntaxFactory; + [Trait(Traits.Feature, Traits.Features.Formatting)] public class FormattingEngineElasticTriviaTests : CSharpFormattingTestBase { @@ -40,40 +43,40 @@ class A class B { }"; - var compilation = SyntaxFactory.CompilationUnit( - externs: [SyntaxFactory.ExternAliasDirective("A1")], + var compilation = CompilationUnit( + externs: [ExternAliasDirective("A1")], usings: default, - attributeLists: [SyntaxFactory.AttributeList( - SyntaxFactory.Token( - [SyntaxFactory.Trivia( - SyntaxFactory.LineDirectiveTrivia( - SyntaxFactory.Literal("99", 99), false))], + attributeLists: [AttributeList( + Token( + [Trivia( + LineDirectiveTrivia( + Literal("99", 99), false))], SyntaxKind.OpenBracketToken, - SyntaxFactory.TriviaList()), - SyntaxFactory.AttributeTargetSpecifier( - SyntaxFactory.Identifier("assembly")), - [SyntaxFactory.Attribute( - SyntaxFactory.ParseName("My"))], - SyntaxFactory.Token( + TriviaList()), + AttributeTargetSpecifier( + Identifier("assembly")), + [Attribute( + ParseName("My"))], + Token( SyntaxKind.CloseBracketToken))], members: [ - SyntaxFactory.ClassDeclaration( + ClassDeclaration( default, modifiers: [], - SyntaxFactory.Identifier("My"), + Identifier("My"), null, - SyntaxFactory.BaseList([SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("System.Attribute"))]), + BaseList([SimpleBaseType(ParseTypeName("System.Attribute"))]), default, default), - SyntaxFactory.ClassDeclaration("A"), - SyntaxFactory.ClassDeclaration( + ClassDeclaration("A"), + ClassDeclaration( attributeLists: [ - SyntaxFactory.AttributeList([ - SyntaxFactory.Attribute( - SyntaxFactory.ParseName("My"))])], + AttributeList([ + Attribute( + ParseName("My"))])], modifiers: [], - identifier: SyntaxFactory.Identifier("B"), + identifier: Identifier("B"), typeParameterList: null, baseList: null, constraintClauses: default, @@ -104,7 +107,7 @@ public class SomeAttribute : System.Attribute { } var workspace = new AdhocWorkspace(); var generator = SyntaxGenerator.GetGenerator(workspace, LanguageNames.CSharp); - var root = SyntaxFactory.ParseCompilationUnit(text); + var root = ParseCompilationUnit(text); var decl = generator.GetDeclaration(root.DescendantNodes().OfType().First(vd => vd.Identifier.Text == "f2")); var newDecl = generator.AddAttributes(decl, generator.Attribute("Some")).WithAdditionalAnnotations(Formatter.Annotation); var newRoot = root.ReplaceNode(decl, newDecl); @@ -142,34 +145,34 @@ public void FormatElasticTriviaBetweenPropertiesWithoutAccessors() string MyProperty => ""42""; }"; - var property = SyntaxFactory.PropertyDeclaration( + var property = PropertyDeclaration( attributeLists: default, modifiers: [], - type: SyntaxFactory.PredefinedType( - SyntaxFactory.Token( + type: PredefinedType( + Token( SyntaxKind.StringKeyword)), explicitInterfaceSpecifier: null, - identifier: SyntaxFactory.Identifier("MyProperty"), + identifier: Identifier("MyProperty"), accessorList: null, expressionBody: - SyntaxFactory.ArrowExpressionClause( - SyntaxFactory.LiteralExpression( + ArrowExpressionClause( + LiteralExpression( SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal("42"))), + Literal("42"))), initializer: null, - semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + semicolonToken: SemicolonToken); - var compilation = SyntaxFactory.CompilationUnit( + var compilation = CompilationUnit( externs: default, usings: default, attributeLists: default, - members: SyntaxFactory.List( + members: List( new MemberDeclarationSyntax[] { - SyntaxFactory.ClassDeclaration( + ClassDeclaration( attributeLists: default, modifiers: [], - identifier: SyntaxFactory.Identifier("PropertyTest"), + identifier: Identifier("PropertyTest"), typeParameterList: null, baseList: null, constraintClauses: default, diff --git a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs index feae8ef98e884..d960a5d90eaec 100644 --- a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs +++ b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs @@ -5,11 +5,9 @@ #nullable disable using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Formatting; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Test.Utilities; @@ -19,6 +17,8 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Formatting { + using static CSharpSyntaxTokens; + [Trait(Traits.Feature, Traits.Features.Formatting)] public class FormattingTests : CSharpFormattingTestBase { @@ -4638,7 +4638,7 @@ public void FormatArbitaryNode() var property = SyntaxFactory.PropertyDeclaration( attributeLists: [], - [SyntaxFactory.Token(SyntaxKind.PublicKeyword)], + [PublicKeyword], SyntaxFactory.ParseTypeName("int"), null, SyntaxFactory.Identifier("Prop"), diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs b/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs index 9a331882f074f..0cba06c47234c 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs @@ -260,7 +260,7 @@ public void EndBatchBuild() _batchBuildLogger?.SetProjectAndLog(projectInstance.FullPath, log); - var buildRequestData = new MSB.Execution.BuildRequestData(projectInstance, targets.ToArray()); + var buildRequestData = new MSB.Execution.BuildRequestData(projectInstance, [.. targets]); var result = await BuildAsync(buildRequestData, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs b/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs index f188a353ef30f..4bfd90f760c37 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs @@ -164,7 +164,7 @@ private void EnsureMSBuildLoaded(string projectFilePath) } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } /// diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs index ef9950a71ca1b..13e65ac97f128 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs @@ -32,7 +32,6 @@ internal static class Extensions public static IEnumerable GetProjectReferences(this MSB.Execution.ProjectInstance executedProject) => executedProject .GetItems(ItemNames.ProjectReference) - .Where(i => i.ReferenceOutputAssemblyIsTrue()) .Select(CreateProjectFileReference); public static ImmutableArray GetPackageReferences(this MSB.Execution.ProjectInstance executedProject) @@ -48,14 +47,14 @@ public static ImmutableArray GetPackageReferences(this MSB.Exe references.Add(packageReference); } - return references.ToImmutableArray(); + return [.. references]; } /// /// Create a from a ProjectReference node in the MSBuild file. /// private static ProjectFileReference CreateProjectFileReference(MSB.Execution.ProjectItemInstance reference) - => new(reference.EvaluatedInclude, reference.GetAliases()); + => new(reference.EvaluatedInclude, reference.GetAliases(), reference.ReferenceOutputAssemblyIsTrue()); public static ImmutableArray GetAliases(this MSB.Framework.ITaskItem item) { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs index 53deba14cf6e3..92fb55d25e1b1 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs @@ -39,7 +39,7 @@ protected ProjectFile(ProjectFileLoader loader, MSB.Evaluation.Project? loadedPr Log = log; } - public ImmutableArray GetDiagnosticLogItems() => Log.ToImmutableArray(); + public ImmutableArray GetDiagnosticLogItems() => [.. Log]; protected abstract SourceCodeKind GetSourceCodeKind(string documentFileName); public abstract string GetDocumentExtension(SourceCodeKind kind); @@ -68,11 +68,11 @@ public async Task> GetProjectFileInfosAsync(Canc // each value, and build the project. var targetFrameworks = targetFrameworksValue.Split(';'); - var results = ImmutableArray.CreateBuilder(targetFrameworks.Length); if (!_loadedProject.GlobalProperties.TryGetValue(PropertyNames.TargetFramework, out var initialGlobalTargetFrameworkValue)) initialGlobalTargetFrameworkValue = null; + var results = new FixedSizeArrayBuilder(targetFrameworks.Length); foreach (var targetFramework in targetFrameworks) { _loadedProject.SetGlobalProperty(PropertyNames.TargetFramework, targetFramework); @@ -94,7 +94,7 @@ public async Task> GetProjectFileInfosAsync(Canc _loadedProject.ReevaluateIfNecessary(); - return results.ToImmutable(); + return results.MoveToImmutable(); } else { @@ -252,7 +252,7 @@ private ImmutableArray GetRelativeFolders(MSB.Framework.ITaskItem docume var linkPath = documentItem.GetMetadata(MetadataNames.Link); if (!RoslynString.IsNullOrEmpty(linkPath)) { - return PathUtilities.GetDirectoryName(linkPath).Split(PathUtilities.DirectorySeparatorChar, PathUtilities.AltDirectorySeparatorChar).ToImmutableArray(); + return [.. PathUtilities.GetDirectoryName(linkPath).Split(PathUtilities.DirectorySeparatorChar, PathUtilities.AltDirectorySeparatorChar)]; } else { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileReference.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileReference.cs index 6ed6024473d6a..fb8d3a216e32a 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileReference.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileReference.cs @@ -27,12 +27,19 @@ internal sealed class ProjectFileReference [DataMember(Order = 1)] public ImmutableArray Aliases { get; } - public ProjectFileReference(string path, ImmutableArray aliases) + /// + /// The value of . + /// + [DataMember(Order = 2)] + public bool ReferenceOutputAssembly { get; } + + public ProjectFileReference(string path, ImmutableArray aliases, bool referenceOutputAssembly) { Debug.Assert(!aliases.IsDefault); - this.Path = path; - this.Aliases = aliases; + Path = path; + Aliases = aliases; + ReferenceOutputAssembly = referenceOutputAssembly; } } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs b/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs index e50bf7d57852b..0bdbcd428bba0 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs @@ -149,7 +149,7 @@ public async ValueTask DisposeAsync() // may try to mutate the list while we're enumerating. using (await _gate.DisposableWaitAsync().ConfigureAwait(false)) { - processesToDispose = _processes.Values.ToList(); + processesToDispose = [.. _processes.Values]; _processes.Clear(); } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs index c20e6b522a7a4..19c234bb6c285 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs @@ -166,7 +166,7 @@ public async Task> LoadAsync(CancellationToken cance } } - return results.ToImmutable(); + return results.ToImmutableAndClear(); } private async Task> LoadProjectFileInfosAsync(string projectPath, DiagnosticReportingOptions reportingOptions, CancellationToken cancellationToken) @@ -435,7 +435,7 @@ private IEnumerable ResolveAnalyzerReferences(CommandLineArgu private ImmutableArray CreateDocumentInfos(IReadOnlyList documentFileInfos, ProjectId projectId, Encoding? encoding) { - var results = ImmutableArray.CreateBuilder(); + var results = new FixedSizeArrayBuilder(documentFileInfos.Count); foreach (var info in documentFileInfos) { @@ -453,7 +453,7 @@ private ImmutableArray CreateDocumentInfos(IReadOnlyList GetUnresolvedMetadataReferenc } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private ImmutableArray GetMetadataReferences() @@ -173,7 +173,7 @@ private ImmutableArray GetMetadataReferences() } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private ImmutableHashSet GetProjectReferences() @@ -209,26 +209,35 @@ private async Task ResolveReferencesAsync(ProjectId id, Proj continue; } - // If we don't know how to load a project (that is, it's not a language we support), we can still - // attempt to verify that its output exists on disk and is included in our set of metadata references. - // If it is, we'll just leave it in place. - if (!IsProjectLoadable(projectReferencePath) && - await VerifyUnloadableProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false)) - { - continue; - } - - // If metadata is preferred, see if the project reference's output exists on disk and is included - // in our metadata references. If it is, don't create a project reference; we'll just use the metadata. - if (_preferMetadataForReferencesOfDiscoveredProjects && - await VerifyProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false)) + if (projectFileReference.ReferenceOutputAssembly) { - continue; + // If we don't know how to load a project (that is, it's not a language we support), we can still + // attempt to verify that its output exists on disk and is included in our set of metadata references. + // If it is, we'll just leave it in place. + if (!IsProjectLoadable(projectReferencePath) && + await VerifyUnloadableProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false)) + { + continue; + } + + // If metadata is preferred, see if the project reference's output exists on disk and is included + // in our metadata references. If it is, don't create a project reference; we'll just use the metadata. + if (_preferMetadataForReferencesOfDiscoveredProjects && + await VerifyProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false)) + { + continue; + } + + // Finally, we'll try to load and reference the project. + if (await TryLoadAndAddReferenceAsync(id, projectReferencePath, aliases, builder, cancellationToken).ConfigureAwait(false)) + { + continue; + } } - - // Finally, we'll try to load and reference the project. - if (await TryLoadAndAddReferenceAsync(id, projectReferencePath, aliases, builder, cancellationToken).ConfigureAwait(false)) + else { + // Load the project but do not add a reference: + _ = await LoadProjectInfosFromPathAsync(projectReferencePath, _discoveredProjectOptions, cancellationToken).ConfigureAwait(false); continue; } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs index d22ddc0d0a33e..35e8f99bf2704 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs @@ -457,7 +457,7 @@ protected override void ApplyDocumentAdded(DocumentInfo info, SourceText text) var fileName = Path.ChangeExtension(info.Name, extension); var relativePath = (info.Folders != null && info.Folders.Count > 0) - ? Path.Combine(Path.Combine(info.Folders.ToArray()), fileName) + ? Path.Combine(Path.Combine([.. info.Folders]), fileName) : fileName; var fullPath = GetAbsolutePath(relativePath, Path.GetDirectoryName(project.FilePath)!); @@ -647,7 +647,9 @@ protected override void ApplyProjectReferenceAdded(ProjectId projectId, ProjectR var project = this.CurrentSolution.GetProject(projectReference.ProjectId); if (project?.FilePath is not null) { - _applyChangesProjectFile.AddProjectReferenceAsync(project.Name, new ProjectFileReference(project.FilePath, projectReference.Aliases), CancellationToken.None).Wait(); + // Only "ReferenceOutputAssembly=true" project references are represented in the workspace: + var reference = new ProjectFileReference(project.FilePath, projectReference.Aliases, referenceOutputAssembly: true); + _applyChangesProjectFile.AddProjectReferenceAsync(project.Name, reference, CancellationToken.None).Wait(); } this.OnProjectReferenceAdded(projectId, projectReference); diff --git a/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj b/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj index 98ad23cbaab6d..6fb4366c78c83 100644 --- a/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj +++ b/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj @@ -112,8 +112,8 @@ Similarly, we set CopyToOutputDirectory for the regular build and PackageCopyToPutput for the packaging process. --> diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.cs.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.cs.xlf index 9f4793e6c3bc1..a2069b38f5d33 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.cs.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.cs.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + V {0} se nepovedlo najít hostitele sestavení. diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.de.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.de.xlf index 81c26ae58434b..6125fa5918100 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.de.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.de.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + Der Buildhost wurde unter „{0}“ nicht gefunden. diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.es.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.es.xlf index 1991d2eb1855e..9b38ece6f434a 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.es.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.es.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + No se encontró el host de compilación en '{0}' diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.fr.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.fr.xlf index f989e4b3ae526..1ea565215356e 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.fr.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.fr.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + Désolé... Nous n’avons pas pu trouver l’hôte de build sur « {0} » diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.it.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.it.xlf index 605413f8a060b..4996c28f4ab66 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.it.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.it.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + Non è stato possibile trovare l'host di compilazione in '{0}' diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ja.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ja.xlf index 95eb800dbfb67..6813cc30aeb2e 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ja.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ja.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + ビルド ホストが '{0}' で見つかりませんでした diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ko.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ko.xlf index 0fe8f7fdee94f..88c99e8b9aa5c 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ko.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ko.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + '{0}'에서 빌드 호스트를 찾을 수 없습니다. diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pl.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pl.xlf index 6dc43d8237197..a46d4b3f5b141 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pl.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pl.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + Nie można odnaleźć hosta kompilacji w lokalizacji „{0}” diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pt-BR.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pt-BR.xlf index 6bb7f41a3d416..edf0d856b64b9 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pt-BR.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pt-BR.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + Não foi possível encontrar o host do build em "{0}" diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ru.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ru.xlf index 9bf6cc048a802..1380789379b15 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ru.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ru.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + Не удалось найти хост сборки в "{0}" diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.tr.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.tr.xlf index 043e0a112b301..3c9da2a38945e 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.tr.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.tr.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + '{0}' konumunda derleme konağı bulunamadı diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hans.xlf b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hans.xlf index 36348690a317d..e0f9b88692ee0 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hans.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hans.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{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 642d6583b9f4d..36b7d71732fb7 100644 --- a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hant.xlf +++ b/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hant.xlf @@ -64,7 +64,7 @@ The build host could not be found at '{0}' - The build host could not be found at '{0}' + 在 '{0}' 找不到組建主機 diff --git a/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs b/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs index f8178c6c5933f..a40000b3ff50b 100644 --- a/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs +++ b/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs @@ -52,7 +52,7 @@ private SyntaxNode CaseCorrect(SemanticModel? semanticModel, SyntaxNode root, Im using (Logger.LogBlock(FunctionId.CaseCorrection_AddReplacements, cancellationToken)) { - AddReplacements(semanticModel, root, normalizedSpanCollection.ToImmutableArray(), replacements, cancellationToken); + AddReplacements(semanticModel, root, [.. normalizedSpanCollection], replacements, cancellationToken); } using (Logger.LogBlock(FunctionId.CaseCorrection_ReplaceTokens, cancellationToken)) diff --git a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs index f112e3dbb5cf9..176d0ebd7aac7 100644 --- a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.ObsoleteSymbol; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.ReassignedVariable; using Microsoft.CodeAnalysis.Remote; @@ -139,6 +140,7 @@ public static async Task AddClassificationsInCurrentProcessAsync( { var classificationService = document.GetRequiredLanguageService(); var reassignedVariableService = document.GetRequiredLanguageService(); + var obsoleteSymbolService = document.GetRequiredLanguageService(); var extensionManager = document.Project.Solution.Services.GetRequiredService(); var classifiers = classificationService.GetDefaultSyntaxClassifiers(); @@ -155,6 +157,13 @@ await classificationService.AddSemanticClassificationsAsync( foreach (var span in reassignedVariableSpans) result.Add(new ClassifiedSpan(span, ClassificationTypeNames.ReassignedVariable)); } + + if (options.ClassifyObsoleteSymbols) + { + var obsoleteSymbolSpans = await obsoleteSymbolService.GetLocationsAsync(document, textSpans, cancellationToken).ConfigureAwait(false); + foreach (var span in obsoleteSymbolSpans) + result.Add(new ClassifiedSpan(span, ClassificationTypeNames.ObsoleteSymbol)); + } } else if (type == ClassificationType.EmbeddedLanguage) { diff --git a/src/Workspaces/Core/Portable/Classification/ClassificationOptions.cs b/src/Workspaces/Core/Portable/Classification/ClassificationOptions.cs index ef812b4362d53..e35ff55dd5f1a 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassificationOptions.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassificationOptions.cs @@ -10,9 +10,10 @@ namespace Microsoft.CodeAnalysis.Classification; internal readonly record struct ClassificationOptions { [DataMember] public bool ClassifyReassignedVariables { get; init; } = false; + [DataMember] public bool ClassifyObsoleteSymbols { get; init; } = true; [DataMember] public bool ColorizeRegexPatterns { get; init; } = true; [DataMember] public bool ColorizeJsonPatterns { get; init; } = true; - [DataMember] public bool ForceFrozenPartialSemanticsForCrossProcessOperations { get; init; } = false; + [DataMember] public bool FrozenPartialSemantics { get; init; } = false; public ClassificationOptions() { diff --git a/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs b/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs index 27bce3937550a..e8a52b10f219f 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs @@ -11,7 +11,7 @@ public static class ClassificationTypeNames /// /// Additive classifications types supply additional context to other classifications. /// - public static ImmutableArray AdditiveTypeNames { get; } = [StaticSymbol, ReassignedVariable, TestCode]; + public static ImmutableArray AdditiveTypeNames { get; } = [StaticSymbol, ReassignedVariable, ObsoleteSymbol, TestCode]; public static ImmutableArray AllTypeNames { get; } = [ @@ -28,6 +28,7 @@ public static class ClassificationTypeNames WhiteSpace, Text, ReassignedVariable, + ObsoleteSymbol, StaticSymbol, PreprocessorText, Punctuation, @@ -112,6 +113,7 @@ public static class ClassificationTypeNames public const string Text = "text"; internal const string ReassignedVariable = "reassigned variable"; + internal const string ObsoleteSymbol = "obsolete symbol"; public const string StaticSymbol = "static symbol"; public const string PreprocessorText = "preprocessor text"; diff --git a/src/Workspaces/Core/Portable/Classification/Classifier.cs b/src/Workspaces/Core/Portable/Classification/Classifier.cs index 7a4fdb6b15726..83cbbd070f4a5 100644 --- a/src/Workspaces/Core/Portable/Classification/Classifier.cs +++ b/src/Workspaces/Core/Portable/Classification/Classifier.cs @@ -77,7 +77,6 @@ internal static IEnumerable GetClassifiedSpans( { var projectServices = services.GetLanguageServices(semanticModel.Language); var classificationService = projectServices.GetRequiredService(); - var embeddedLanguageService = projectServices.GetRequiredService(); var syntaxClassifiers = classificationService.GetDefaultSyntaxClassifiers(); @@ -94,8 +93,12 @@ internal static IEnumerable GetClassifiedSpans( classificationService.AddSemanticClassifications(semanticModel, textSpan, getNodeClassifiers, getTokenClassifiers, semanticClassifications, options, cancellationToken); // intentionally adding to the semanticClassifications array here. - if (includedEmbeddedClassifications && project != null) + if (includedEmbeddedClassifications + && project != null + && projectServices.GetService() is { } embeddedLanguageService) + { embeddedLanguageService.AddEmbeddedLanguageClassifications(services, project, semanticModel, textSpan, options, semanticClassifications, cancellationToken); + } var allClassifications = new List(semanticClassifications.Where(s => s.TextSpan.OverlapsWith(textSpan))); var semanticSet = semanticClassifications.Select(s => s.TextSpan).ToSet(); diff --git a/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs b/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs index 1d45eb4e6ab7b..dd8e71e8f7906 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs @@ -139,7 +139,7 @@ private static ImmutableArray MergeClassifiedSpans( // be gaps in what it produces. Fill in those gaps so we have *all* parts of the span classified properly. using var _2 = Classifier.GetPooledList(out var filledInSpans); FillInClassifiedSpanGaps(widenedSpan.Start, mergedSpans, filledInSpans); - return filledInSpans.ToImmutableArray(); + return [.. filledInSpans]; } private static readonly Comparison s_spanComparison = static (s1, s2) => s1.TextSpan.Start - s2.TextSpan.Start; diff --git a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs index b14098c46acd5..8cdf45d02e60f 100644 --- a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs @@ -79,7 +79,7 @@ internal static SerializableClassifiedSpans Dehydrate(ImmutableArray classifiedSpans, Dictionary classificationTypeToId) { using var _1 = ArrayBuilder.GetInstance(out var classificationTypes); - using var _2 = ArrayBuilder.GetInstance(capacity: classifiedSpans.Length * 3, out var classificationTriples); + var classificationTriples = new FixedSizeArrayBuilder(classifiedSpans.Length * 3); foreach (var classifiedSpan in classifiedSpans) { @@ -99,7 +99,7 @@ private static SerializableClassifiedSpans Dehydrate(ImmutableArray classifiedSpans) diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.Worker.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.Worker.cs index 6023c061d8b2e..a07c282bfca92 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.Worker.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.Worker.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Classification.Classifiers; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Classification; @@ -95,10 +96,9 @@ private void AddClassification(TextSpan textSpan, string type) private void ProcessNodes() { - while (_pendingNodes.Count > 0) + while (_pendingNodes.TryPop(out var nodeOrToken)) { _cancellationToken.ThrowIfCancellationRequested(); - var nodeOrToken = _pendingNodes.Pop(); if (nodeOrToken.FullSpan.IntersectsWith(_textSpan)) { diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs index 90a4ceebe28ab..c5af10841e9ce 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs @@ -244,9 +244,8 @@ int ComputeCommonRightWidth() private static bool TryGetStackTopNodeOrToken(Stack stack, out SyntaxNodeOrToken syntaxNodeOrToken) { - while (stack.Count > 0) + while (stack.TryPop(out var topEnumerator)) { - var topEnumerator = stack.Pop(); if (topEnumerator.MoveNext()) { syntaxNodeOrToken = topEnumerator.Current; @@ -262,9 +261,8 @@ private static bool TryGetStackTopNodeOrToken(Stack private static bool TryGetStackTopNodeOrToken(Stack stack, out SyntaxNodeOrToken syntaxNodeOrToken) { - while (stack.Count > 0) + while (stack.TryPop(out var topEnumerator)) { - var topEnumerator = stack.Pop(); if (topEnumerator.MoveNext()) { syntaxNodeOrToken = topEnumerator.Current; diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index 360bc91b95a4a..28a3af953bf7f 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -284,7 +284,7 @@ protected virtual async Task> ComputeOperations var changedSolution = await GetChangedSolutionAsync(CodeAnalysisProgress.None, cancellationToken).ConfigureAwait(false); return changedSolution == null ? [] - : SpecializedCollections.SingletonEnumerable(new ApplyChangesOperation(changedSolution)); + : [new ApplyChangesOperation(changedSolution)]; } #pragma warning disable RS0030 // Do not use banned APIs diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeActionWithOptions.cs b/src/Workspaces/Core/Portable/CodeActions/CodeActionWithOptions.cs index e93f95524ee93..5914b7fbd7a7b 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeActionWithOptions.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeActionWithOptions.cs @@ -38,9 +38,7 @@ public abstract class CodeActionWithOptions : CodeAction Solution originalSolution, object? options, IProgress progress, CancellationToken cancellationToken) { if (options == null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var operations = await this.ComputeOperationsAsync(options, progress, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs b/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs index a7bec9d9175e8..2a65deb55737d 100644 --- a/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs +++ b/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs @@ -530,7 +530,7 @@ private ImmutableArray GetSpans( // Remove the spans we should not touch from the requested spans and return that final set. var result = NormalizedTextSpanCollection.Difference(requestedSpans, spansToAvoid); - return result.ToImmutableArray(); + return [.. result]; } private async Task IterateAllCodeCleanupProvidersAsync( @@ -594,7 +594,7 @@ private string GetCodeCleanerTypeName(ICodeCleanupProvider codeCleaner) private static SyntaxNode InjectAnnotations(SyntaxNode node, Dictionary> map) { var tokenMap = map.ToDictionary(p => p.Key, p => p.Value); - return node.ReplaceTokens(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o].ToArray())); + return node.ReplaceTokens(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations([.. tokenMap[o]])); } private static bool TryCreateTextSpan(int start, int end, out TextSpan span) diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs index cb76d362524a5..a7e5192a9bdf0 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs @@ -175,7 +175,7 @@ private static async Task> GetAllChangedDocumentsInDiag } } - return changedDocuments.ToImmutable(); + return changedDocuments.ToImmutableAndClear(); }, cancellationToken)); } @@ -188,7 +188,7 @@ private static async Task> GetAllChangedDocumentsInDiag foreach (var task in tasks) result.AddRange(await task.ConfigureAwait(false)); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } /// @@ -236,9 +236,8 @@ private static Action> GetRegisterCodeFix { using var _ = ArrayBuilder.GetInstance(out var builder); builder.Push(action); - while (builder.Count > 0) + while (builder.TryPop(out var currentAction)) { - var currentAction = builder.Pop(); if (currentAction is { EquivalenceKey: var equivalenceKey } && codeActionEquivalenceKey == equivalenceKey) { diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs index dc9da72490607..2c7b4ed56d743 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs @@ -78,8 +78,7 @@ internal static async Task>(); diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.cs index 7f66271166b67..4e74e7dc8d80d 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.cs @@ -85,8 +85,9 @@ string IFixAllContext.GetDefaultFixAllTitle() IFixAllContext IFixAllContext.With( Optional<(Document? document, Project project)> documentAndProject, Optional scope, - Optional codeActionEquivalenceKey) - => this.With(documentAndProject, scope, codeActionEquivalenceKey); + Optional codeActionEquivalenceKey, + Optional cancellationToken) + => this.With(documentAndProject, scope, codeActionEquivalenceKey, cancellationToken); #endregion /// @@ -330,23 +331,20 @@ private async Task> GetProjectDiagnosticsAsync(Projec /// Gets a new with the given cancellationToken. /// public FixAllContext WithCancellationToken(CancellationToken cancellationToken) - { - // TODO: We should change this API to be a virtual method, as the class is not sealed. - if (this.CancellationToken == cancellationToken) - { - return this; - } - - return new FixAllContext(State, this.Progress, cancellationToken); - } + => With(cancellationToken: cancellationToken); internal FixAllContext With( Optional<(Document? document, Project project)> documentAndProject = default, Optional scope = default, - Optional codeActionEquivalenceKey = default) + Optional codeActionEquivalenceKey = default, + Optional cancellationToken = default) { var newState = State.With(documentAndProject, scope, codeActionEquivalenceKey); - return State == newState ? this : new FixAllContext(newState, this.Progress, CancellationToken); + var newCancellationToken = cancellationToken.HasValue ? cancellationToken.Value : this.CancellationToken; + + return State == newState && CancellationToken == newCancellationToken + ? this + : new FixAllContext(newState, this.Progress, newCancellationToken); } internal Task>> GetDocumentDiagnosticsToFixAsync() diff --git a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DefaultFixAllProviderHelpers.cs b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DefaultFixAllProviderHelpers.cs index 5742106da6675..158596febdcc8 100644 --- a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DefaultFixAllProviderHelpers.cs +++ b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DefaultFixAllProviderHelpers.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeFixesAndRefactorings; +using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -26,6 +26,12 @@ internal static class DefaultFixAllProviderHelpers Func, Task> fixAllContextsAsync) where TFixAllContext : IFixAllContext { + + // We're about to do a lot of computation to compute all the diagnostics needed and to perform the + // changes. Keep this solution alive on the OOP side so that we never drop it and then resync it + // (which would cause us to drop/recreate compilations, skeletons and sg docs. + using var _ = await RemoteKeepAliveSession.CreateAsync(fixAllContext.Solution, fixAllContext.CancellationToken).ConfigureAwait(false); + var solution = fixAllContext.Scope switch { FixAllScope.Document or FixAllScope.ContainingMember or FixAllScope.ContainingType @@ -39,7 +45,7 @@ FixAllScope.Document or FixAllScope.ContainingMember or FixAllScope.ContainingTy return null; return CodeAction.Create( - title, c => Task.FromResult(solution)); + title, _ => Task.FromResult(solution)); } private static Task GetDocumentFixesAsync( diff --git a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs index 0688f7cdd3612..37248a1e7031f 100644 --- a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs +++ b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs @@ -10,7 +10,9 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -40,32 +42,58 @@ internal static class DocumentBasedFixAllProviderHelpers var workItemCount = fixAllKind == FixAllKind.CodeFix ? 3 : 2; progressTracker.AddItems(fixAllContexts.Length * workItemCount); - // Process each context one at a time, allowing us to dump any information we computed for each once done with it. - var currentSolution = solution; - foreach (var fixAllContext in fixAllContexts) + using var _1 = PooledDictionary.GetInstance(out var allContextsDocIdToNewRootOrText); { - Contract.ThrowIfFalse(fixAllContext.Scope is FixAllScope.Document or FixAllScope.Project - or FixAllScope.ContainingMember or FixAllScope.ContainingType); - currentSolution = await FixSingleContextAsync(currentSolution, fixAllContext, progressTracker, getFixedDocumentsAsync).ConfigureAwait(false); - } + // First, iterate over all contexts, and collect all the changes for each of them. We'll be making a lot of + // calls to the remote server to compute diagnostics and changes. So keep a single connection alive to it + // so we never resync or recompute anything. + using var _2 = await RemoteKeepAliveSession.CreateAsync(solution, originalFixAllContext.CancellationToken).ConfigureAwait(false); - return currentSolution; - } - - private static async Task FixSingleContextAsync( - Solution currentSolution, - TFixAllContext fixAllContext, - IProgress progressTracker, - Func, Task>> getFixedDocumentsAsync) - where TFixAllContext : IFixAllContext - { - // First, compute and apply the fixes. - var docIdToNewRootOrText = await getFixedDocumentsAsync(fixAllContext, progressTracker).ConfigureAwait(false); + foreach (var fixAllContext in fixAllContexts) + { + Contract.ThrowIfFalse( + fixAllContext.Scope is FixAllScope.Document or FixAllScope.Project or FixAllScope.ContainingMember or FixAllScope.ContainingType); + + // TODO: consider computing this in parallel. + var singleContextDocIdToNewRootOrText = await getFixedDocumentsAsync(fixAllContext, progressTracker).ConfigureAwait(false); + + // Note: it is safe to blindly add the dictionary for a particular context to the full dictionary. Each + // dictionary will only update documents within that context, and each context represents a distinct + // project, so these should all be distinct without collisions. However, to be very safe, we use an + // overwriting policy here to ensure nothing causes any problems here. + foreach (var kvp in singleContextDocIdToNewRootOrText) + allContextsDocIdToNewRootOrText[kvp.Key] = kvp.Value; + } + } - // Then, cleanup the new doc roots, and apply the results to the solution. - currentSolution = await CleanupAndApplyChangesAsync(progressTracker, currentSolution, docIdToNewRootOrText, fixAllContext.CancellationToken).ConfigureAwait(false); + // Next, go and insert those all into the solution so all the docs in this particular project point at + // the new trees (or text). At this point though, the trees have not been cleaned up. We don't cleanup + // the documents as they are created, or one at a time as we add them, as that would cause us to run + // cleanup on N different solution forks (which would be very expensive). Instead, by adding all the + // changed documents to one solution, and then cleaning *those* we only perform cleanup semantics on one + // forked solution. + var currentSolution = solution; + foreach (var (docId, (newRoot, newText)) in allContextsDocIdToNewRootOrText) + { + currentSolution = newRoot != null + ? currentSolution.WithDocumentSyntaxRoot(docId, newRoot) + : currentSolution.WithDocumentText(docId, newText!); + } - return currentSolution; + { + // We're about to making a ton of calls to this new solution, including expensive oop calls to get up to + // date compilations, skeletons and SG docs. Create and pin this solution so that all remote calls operate + // on the same fork and do not cause the forked solution to be created and dropped repeatedly. + using var _2 = await RemoteKeepAliveSession.CreateAsync(currentSolution, originalFixAllContext.CancellationToken).ConfigureAwait(false); + + var finalSolution = await CleanupAndApplyChangesAsync( + progressTracker, + currentSolution, + allContextsDocIdToNewRootOrText, + originalFixAllContext.CancellationToken).ConfigureAwait(false); + + return finalSolution; + } } /// @@ -83,38 +111,18 @@ private static async Task CleanupAndApplyChangesAsync( if (docIdToNewRootOrText.Count > 0) { - // Next, go and insert those all into the solution so all the docs in this particular project point at - // the new trees (or text). At this point though, the trees have not been cleaned up. We don't cleanup - // the documents as they are created, or one at a time as we add them, as that would cause us to run - // cleanup on N different solution forks (which would be very expensive). Instead, by adding all the - // changed documents to one solution, and hten cleaning *those* we only perform cleanup semantics on one - // forked solution. - foreach (var (docId, (newRoot, newText)) in docIdToNewRootOrText) - { - currentSolution = newRoot != null - ? currentSolution.WithDocumentSyntaxRoot(docId, newRoot) - : currentSolution.WithDocumentText(docId, newText!); - } - // Next, go and cleanup any trees we inserted. Once we clean the document, we get the text of it and - // insert that back into the final solution. This way we can release both the original fixed tree, and - // the cleaned tree (both of which can be much more expensive than just text). + // Next, go and cleanup any trees we inserted. Once we clean the document, we get the text of it and insert + // that back into the final solution. This way we can release both the original fixed tree, and the cleaned + // tree (both of which can be much more expensive than just text). // // Do this in parallel across all the documents that were fixed. - using var _2 = ArrayBuilder>.GetInstance(out var tasks); + using var _2 = ArrayBuilder>.GetInstance(out var tasks); foreach (var (docId, (newRoot, _)) in docIdToNewRootOrText) { if (newRoot != null) - { - var dirtyDocument = currentSolution.GetRequiredDocument(docId); - tasks.Add(Task.Run(async () => - { - var cleanedDocument = await PostProcessCodeAction.Instance.PostProcessChangesAsync(dirtyDocument, cancellationToken).ConfigureAwait(false); - var cleanedText = await cleanedDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - return (dirtyDocument.Id, cleanedText); - }, cancellationToken)); - } + tasks.Add(GetCleanedDocumentAsync(currentSolution.GetRequiredDocument(docId), cancellationToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); @@ -128,6 +136,15 @@ private static async Task CleanupAndApplyChangesAsync( } return currentSolution; + + static async Task<(DocumentId docId, SourceText sourceText)> GetCleanedDocumentAsync(Document dirtyDocument, CancellationToken cancellationToken) + { + await Task.Yield(); + + var cleanedDocument = await PostProcessCodeAction.Instance.PostProcessChangesAsync(dirtyDocument, cancellationToken).ConfigureAwait(false); + var cleanedText = await cleanedDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + return (dirtyDocument.Id, cleanedText); + } } /// diff --git a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/IFixAllContext.cs b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/IFixAllContext.cs index 597ecd87d8181..13c705805da00 100644 --- a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/IFixAllContext.cs +++ b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/IFixAllContext.cs @@ -3,12 +3,8 @@ // See the LICENSE file in the project root for more information. using System; -using System.Diagnostics; using System.Threading; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.Utilities; -using FixAllScope = Microsoft.CodeAnalysis.CodeFixes.FixAllScope; +using Microsoft.CodeAnalysis.CodeFixes; namespace Microsoft.CodeAnalysis.CodeFixesAndRefactorings; @@ -32,5 +28,6 @@ internal interface IFixAllContext IFixAllContext With( Optional<(Document? document, Project project)> documentAndProject = default, Optional scope = default, - Optional codeActionEquivalenceKey = default); + Optional codeActionEquivalenceKey = default, + Optional cancellationToken = default); } diff --git a/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/FixAllContext.cs b/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/FixAllContext.cs index f3a2419227fac..c6b0c8d0611e6 100644 --- a/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/FixAllContext.cs +++ b/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/FixAllContext.cs @@ -75,8 +75,9 @@ internal sealed class FixAllContext : IFixAllContext IFixAllContext IFixAllContext.With( Optional<(Document? document, Project project)> documentAndProject, Optional scope, - Optional codeActionEquivalenceKey) - => this.With(documentAndProject, scope, codeActionEquivalenceKey); + Optional codeActionEquivalenceKey, + Optional cancellationToken) + => this.With(documentAndProject, scope, codeActionEquivalenceKey, cancellationToken); #endregion internal FixAllContext( @@ -89,19 +90,6 @@ internal FixAllContext( this.CancellationToken = cancellationToken; } - /// - /// Gets a new with the given cancellationToken. - /// - public FixAllContext WithCancellationToken(CancellationToken cancellationToken) - { - if (this.CancellationToken == cancellationToken) - { - return this; - } - - return new FixAllContext(State, this.Progress, cancellationToken); - } - /// /// Gets the spans to fix by document for the for this fix all occurences fix. /// If no spans are specified, it indicates the entire document needs to be fixed. @@ -112,10 +100,15 @@ public Task>>> G internal FixAllContext With( Optional<(Document? document, Project project)> documentAndProject = default, Optional scope = default, - Optional codeActionEquivalenceKey = default) + Optional codeActionEquivalenceKey = default, + Optional cancellationToken = default) { var newState = State.With(documentAndProject, scope, codeActionEquivalenceKey); - return State == newState ? this : new FixAllContext(newState, this.Progress, CancellationToken); + var newCancellationToken = cancellationToken.HasValue ? cancellationToken.Value : this.CancellationToken; + + return State == newState && CancellationToken == newCancellationToken + ? this + : new FixAllContext(newState, this.Progress, newCancellationToken); } internal string GetDefaultFixAllTitle() diff --git a/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/FixAllState.cs b/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/FixAllState.cs index eb9a523195ec9..f587f2af918d6 100644 --- a/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/FixAllState.cs +++ b/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/FixAllState.cs @@ -110,7 +110,7 @@ internal async Task GetAllDiagnostics() Contract.ThrowIfNull(_nonLocals); Contract.ThrowIfTrue(_others.IsDefault); - var builder = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var builder); foreach (var data in _syntaxLocals.Values) - { builder.AddRange(data); - } foreach (var data in _semanticLocals.Values) - { builder.AddRange(data); - } foreach (var data in _nonLocals.Values) - { builder.AddRange(data); - } foreach (var data in _others) - { builder.AddRange(data); - } - return builder.ToImmutableAndFree(); + return builder.ToImmutableAndClear(); } public ImmutableArray GetDocumentDiagnostics(DocumentId documentId, AnalysisKind kind) diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs index 680b935e03483..883c28be7be1a 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs @@ -30,11 +30,11 @@ internal struct DiagnosticAnalysisResultBuilder(Project project, VersionStamp ve private List? _lazyOthers = null; - public readonly ImmutableHashSet DocumentIds => _lazyDocumentsWithDiagnostics == null ? [] : _lazyDocumentsWithDiagnostics.ToImmutableHashSet(); + public readonly ImmutableHashSet DocumentIds => _lazyDocumentsWithDiagnostics == null ? [] : [.. _lazyDocumentsWithDiagnostics]; public readonly ImmutableDictionary> SyntaxLocals => Convert(_lazySyntaxLocals); public readonly ImmutableDictionary> SemanticLocals => Convert(_lazySemanticLocals); public readonly ImmutableDictionary> NonLocals => Convert(_lazyNonLocals); - public readonly ImmutableArray Others => _lazyOthers == null ? [] : _lazyOthers.ToImmutableArray(); + public readonly ImmutableArray Others => _lazyOthers == null ? [] : [.. _lazyOthers]; public void AddExternalSyntaxDiagnostics(DocumentId documentId, IEnumerable diagnostics) { diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index 942a846fa8671..8e3b0c044b186 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -385,7 +385,7 @@ private static async Task> GetPragmaSuppressionAnalyz using var _ = ArrayBuilder.GetInstance(out var diagnosticsBuilder); await AnalyzeDocumentAsync(suppressionAnalyzer, document, documentAnalysisScope.Span, diagnosticsBuilder.Add).ConfigureAwait(false); - return diagnosticsBuilder.ToImmutable(); + return diagnosticsBuilder.ToImmutableAndClear(); } else { @@ -404,7 +404,7 @@ private static async Task> GetPragmaSuppressionAnalyz } await Task.WhenAll(tasks).ConfigureAwait(false); - return bag.ToImmutableArray(); + return [.. bag]; } else { @@ -419,7 +419,7 @@ private static async Task> GetPragmaSuppressionAnalyz await AnalyzeDocumentAsync(suppressionAnalyzer, document, span: null, diagnosticsBuilder.Add).ConfigureAwait(false); } - return diagnosticsBuilder.ToImmutable(); + return diagnosticsBuilder.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Core/Portable/Differencing/LongestCommonSubsequence.cs b/src/Workspaces/Core/Portable/Differencing/LongestCommonSubsequence.cs index a7582bd563536..be69c9dd8d29b 100644 --- a/src/Workspaces/Core/Portable/Differencing/LongestCommonSubsequence.cs +++ b/src/Workspaces/Core/Portable/Differencing/LongestCommonSubsequence.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Differencing; @@ -284,7 +285,7 @@ protected IEnumerable> GetMatchingPairs(TSequence oldSequ Debug.Assert(yEnd > yMid); xEnd--; yEnd--; - yield return new KeyValuePair(xEnd, yEnd); + yield return KeyValuePairUtil.Create(xEnd, yEnd); } x = xStart; diff --git a/src/Workspaces/Core/Portable/Editing/ImportAdder.cs b/src/Workspaces/Core/Portable/Editing/ImportAdder.cs index fce9cd2e5a8ae..c7c09b3f4a6da 100644 --- a/src/Workspaces/Core/Portable/Editing/ImportAdder.cs +++ b/src/Workspaces/Core/Portable/Editing/ImportAdder.cs @@ -20,7 +20,7 @@ public static class ImportAdder private static async ValueTask> GetSpansAsync(Document document, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - return SpecializedCollections.SingletonEnumerable(root.FullSpan); + return [root.FullSpan]; } private static async ValueTask> GetSpansAsync(Document document, SyntaxAnnotation annotation, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs b/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs index 555dfa7b6826f..6615ecbd563fe 100644 --- a/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs +++ b/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs @@ -229,7 +229,7 @@ private async Task AddImportDirectivesFromSymbolAnnotationsAsync( var importContainer = addImportsService.GetImportContainer(root, context, importToSyntax.First().Value, options); // Now remove any imports we think can cause conflicts in that container. - var safeImportsToAdd = GetSafeToAddImports(importToSyntax.Keys.ToImmutableArray(), importContainer, model, cancellationToken); + var safeImportsToAdd = GetSafeToAddImports([.. importToSyntax.Keys], importContainer, model, cancellationToken); var importsToAdd = importToSyntax.Where(kvp => safeImportsToAdd.Contains(kvp.Key)).Select(kvp => kvp.Value).ToImmutableArray(); if (importsToAdd.Length == 0) diff --git a/src/Workspaces/Core/Portable/Editing/SyntaxEditorExtensions.cs b/src/Workspaces/Core/Portable/Editing/SyntaxEditorExtensions.cs index 51c79506734fd..c7111748afc1f 100644 --- a/src/Workspaces/Core/Portable/Editing/SyntaxEditorExtensions.cs +++ b/src/Workspaces/Core/Portable/Editing/SyntaxEditorExtensions.cs @@ -75,4 +75,7 @@ public static void AddInterfaceType(this SyntaxEditor editor, SyntaxNode declara public static void AddBaseType(this SyntaxEditor editor, SyntaxNode declaration, SyntaxNode baseType) => editor.ReplaceNode(declaration, (d, g) => g.AddBaseType(d, baseType)); + + internal static void RemovePrimaryConstructor(this SyntaxEditor editor, SyntaxNode declaration) + => editor.ReplaceNode(declaration, (d, g) => g.RemovePrimaryConstructor(d)); } diff --git a/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs b/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs index e26b4065bb6cf..da35c94ba2347 100644 --- a/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs +++ b/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs @@ -257,16 +257,26 @@ public virtual SyntaxNode OperatorDeclaration( throw new NotImplementedException(); } + private protected abstract SyntaxNode OperatorDeclaration( + string operatorName, + bool isImplicitConversion, + IEnumerable? parameters = null, + SyntaxNode? returnType = null, + Accessibility accessibility = Accessibility.NotApplicable, + DeclarationModifiers modifiers = default, + IEnumerable? statements = null); + /// /// Creates a operator or conversion declaration matching an existing method symbol. /// public SyntaxNode OperatorDeclaration(IMethodSymbol method, IEnumerable? statements = null) { if (method.MethodKind is not (MethodKind.UserDefinedOperator or MethodKind.Conversion)) - throw new ArgumentException("Method is not an operator."); + throw new ArgumentException($"Method kind '{method.MethodKind}' is not an operator."); var decl = OperatorDeclaration( - GetOperatorKind(method), + method.Name, + isImplicitConversion: method.Name is WellKnownMemberNames.ImplicitConversionName, parameters: method.Parameters.Select(p => ParameterDeclaration(p)), returnType: method.ReturnType.IsSystemVoid() ? null : TypeExpression(method.ReturnType, method.RefKind), accessibility: method.DeclaredAccessibility, @@ -276,39 +286,6 @@ public SyntaxNode OperatorDeclaration(IMethodSymbol method, IEnumerable method.Name switch - { - WellKnownMemberNames.ImplicitConversionName => OperatorKind.ImplicitConversion, - WellKnownMemberNames.ExplicitConversionName => OperatorKind.ExplicitConversion, - WellKnownMemberNames.AdditionOperatorName => OperatorKind.Addition, - WellKnownMemberNames.BitwiseAndOperatorName => OperatorKind.BitwiseAnd, - WellKnownMemberNames.BitwiseOrOperatorName => OperatorKind.BitwiseOr, - WellKnownMemberNames.DecrementOperatorName => OperatorKind.Decrement, - WellKnownMemberNames.DivisionOperatorName => OperatorKind.Division, - WellKnownMemberNames.EqualityOperatorName => OperatorKind.Equality, - WellKnownMemberNames.ExclusiveOrOperatorName => OperatorKind.ExclusiveOr, - WellKnownMemberNames.FalseOperatorName => OperatorKind.False, - WellKnownMemberNames.GreaterThanOperatorName => OperatorKind.GreaterThan, - WellKnownMemberNames.GreaterThanOrEqualOperatorName => OperatorKind.GreaterThanOrEqual, - WellKnownMemberNames.IncrementOperatorName => OperatorKind.Increment, - WellKnownMemberNames.InequalityOperatorName => OperatorKind.Inequality, - WellKnownMemberNames.LeftShiftOperatorName => OperatorKind.LeftShift, - WellKnownMemberNames.LessThanOperatorName => OperatorKind.LessThan, - WellKnownMemberNames.LessThanOrEqualOperatorName => OperatorKind.LessThanOrEqual, - WellKnownMemberNames.LogicalNotOperatorName => OperatorKind.LogicalNot, - WellKnownMemberNames.ModulusOperatorName => OperatorKind.Modulus, - WellKnownMemberNames.MultiplyOperatorName => OperatorKind.Multiply, - WellKnownMemberNames.OnesComplementOperatorName => OperatorKind.OnesComplement, - WellKnownMemberNames.RightShiftOperatorName => OperatorKind.RightShift, - WellKnownMemberNames.UnsignedRightShiftOperatorName => OperatorKind.UnsignedRightShift, - WellKnownMemberNames.SubtractionOperatorName => OperatorKind.Subtraction, - WellKnownMemberNames.TrueOperatorName => OperatorKind.True, - WellKnownMemberNames.UnaryNegationOperatorName => OperatorKind.UnaryNegation, - WellKnownMemberNames.UnaryPlusOperatorName => OperatorKind.UnaryPlus, - _ => throw new ArgumentException("Unknown operator kind."), - }; - /// /// Creates a parameter declaration. /// @@ -1071,6 +1048,14 @@ public SyntaxNode RemoveAllAttributes(SyntaxNode declaration) /// internal abstract SyntaxNode RemoveAllComments(SyntaxNode node); + internal SyntaxNode RemovePrimaryConstructor(SyntaxNode declaration) + { + var node = GetPrimaryConstructorParameterList(declaration); + return RemoveNodes(declaration, node is not null ? [node] : []); + } + + internal abstract SyntaxNode? GetPrimaryConstructorParameterList(SyntaxNode declaration); + internal SyntaxNode RemoveLeadingAndTrailingComments(SyntaxNode node) { return node.WithLeadingTrivia(RemoveCommentLines(node.GetLeadingTrivia())) diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs index 37aba259d93a3..485422c8c7ff9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs @@ -74,7 +74,7 @@ internal static async Task> FindAllDeclarationsWithNorma await SearchMetadataReferencesAsync().ConfigureAwait(false); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); async Task SearchCurrentProjectAsync() { diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs index 56120d6a720c9..647ac2afc594f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs @@ -186,7 +186,7 @@ await AddCompilationSourceDeclarationsWithNormalQueryAsync( project, query, criteria, result, cancellationToken).ConfigureAwait(false); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } internal static async Task> FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync( @@ -198,7 +198,7 @@ internal static async Task> FindSourceDeclarationsWithNo await AddCompilationSourceDeclarationsWithNormalQueryAsync( project, query, filter, result, cancellationToken).ConfigureAwait(false); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static async Task> FindSourceDeclarationsWithPatternInCurrentProcessAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs index 7874ddb4d4df4..d489d306ed371 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs @@ -57,10 +57,10 @@ public FindLiteralsSearchEngine( _longValue = BitConverter.DoubleToInt64Bits(f); _searchKind = SearchKind.NumericLiterals; break; - case decimal _: // unsupported + case decimal: // unsupported _searchKind = SearchKind.None; break; - case char _: + case char: _longValue = IntegerUtilities.ToInt64(value); _searchKind = SearchKind.CharacterLiterals; break; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs index 5ac66337bfae1..6ece9e7edac9e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs @@ -15,10 +15,10 @@ internal static partial class BaseTypeFinder public static ImmutableArray FindBaseTypesAndInterfaces(INamedTypeSymbol type) => FindBaseTypes(type).AddRange(type.AllInterfaces); - public static async ValueTask> FindOverriddenAndImplementedMembersAsync( + public static ImmutableArray FindOverriddenAndImplementedMembers( ISymbol symbol, Solution solution, CancellationToken cancellationToken) { - var results = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var results); // This is called for all: class, struct or interface member. results.AddRange(symbol.ExplicitOrImplicitInterfaceImplementations()); @@ -31,7 +31,7 @@ public static async ValueTask> FindOverriddenAndImplemen cancellationToken.ThrowIfCancellationRequested(); // Add to results overridden members only. Do not add hidden members. - if (await SymbolFinder.IsOverrideAsync(solution, symbol, member, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.IsOverride(solution, symbol, member)) { results.Add(member); @@ -64,7 +64,8 @@ public static async ValueTask> FindOverriddenAndImplemen } // Remove duplicates from interface implementations before adding their projects. - return results.ToImmutableAndFree().Distinct(); + results.RemoveDuplicates(); + return results.ToImmutableAndClear(); } private static ImmutableArray FindBaseTypes(INamedTypeSymbol type) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index b2a14157d4adc..380a3c3bf55f0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -35,7 +35,7 @@ public static async Task> GetDependentProjectsAsync( { // namespaces are visible in all projects. if (symbols.Any(static s => s.Kind == SymbolKind.Namespace)) - return projects.ToImmutableArray(); + return [.. projects]; var dependentProjects = await GetDependentProjectsWorkerAsync(solution, symbols, cancellationToken).ConfigureAwait(false); return dependentProjects.WhereAsArray(projects.Contains); @@ -84,7 +84,7 @@ private static async Task> GetDependentProjectsWorkerAsy result.AddRange(filteredProjects.Select(p => p.project)); } - return result.ToImmutableArray(); + return [.. result]; } /// @@ -145,7 +145,7 @@ private static async Task> GetDependentProjectsWorkerAsy // further submissions can bind to them. await AddSubmissionDependentProjectsAsync(solution, symbolOrigination.sourceProject, dependentProjects, cancellationToken).ConfigureAwait(false); - return dependentProjects.ToImmutableArray(); + return [.. dependentProjects]; } private static async Task AddSubmissionDependentProjectsAsync( @@ -191,12 +191,12 @@ private static async Task AddSubmissionDependentProjectsAsync( // and 2, even though 2 doesn't have a direct reference to 1. Hence we need to take // our current set of projects and find the transitive closure over backwards // submission previous references. - var projectIdsToProcess = new Stack(dependentProjects.Select(dp => dp.project.Id)); + using var _ = ArrayBuilder.GetInstance(out var projectIdsToProcess); + foreach (var dependentProject in dependentProjects.Select(dp => dp.project.Id)) + projectIdsToProcess.Push(dependentProject); - while (projectIdsToProcess.Count > 0) + while (projectIdsToProcess.TryPop(out var toProcess)) { - var toProcess = projectIdsToProcess.Pop(); - if (projectIdsToReferencingSubmissionIds.TryGetValue(toProcess, out var submissionIds)) { foreach (var pId in submissionIds) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 79f7ef5d7893a..9e360bd3f47cb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -136,7 +136,7 @@ private static async Task> DescendInheritanceTr await DescendInheritanceTreeInProjectAsync(project).ConfigureAwait(false); } - return result.ToImmutableArray(); + return [.. result]; async Task DescendInheritanceTreeInProjectAsync(Project project) { @@ -472,7 +472,7 @@ private static ImmutableArray OrderTopologically( index++; } - return projectsToExamine.OrderBy((p1, p2) => order[p1.Id] - order[p2.Id]).ToImmutableArray(); + return [.. projectsToExamine.OrderBy((p1, p2) => order[p1.Id] - order[p2.Id])]; } private static ImmutableArray GetProjectsToExamineWorker( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_Remote.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_Remote.cs index d3b33e5d2d680..8c1d6eb9a7dce 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_Remote.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_Remote.cs @@ -91,6 +91,6 @@ private static async Task> RehydrateAsync(Solut builder.AddIfNotNull(namedType); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index 3c26c78c24971..ba3cdc5889a6e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -17,14 +17,42 @@ namespace Microsoft.CodeAnalysis.FindSymbols; +/// +/// Caches information find-references needs associated with each document. Computed and cached so that multiple calls +/// to find-references in a row can share the same data. +/// internal sealed class FindReferenceCache { - private static readonly ConditionalWeakTable s_cache = new(); + private static readonly ConditionalWeakTable> s_cache = new(); - public static FindReferenceCache GetCache(SemanticModel model) - => s_cache.GetValue(model, static model => new(model)); + public static async ValueTask GetCacheAsync(Document document, CancellationToken cancellationToken) + { + var lazy = s_cache.GetValue(document, static document => AsyncLazy.Create(ComputeCacheAsync, document)); + return await lazy.GetValueAsync(cancellationToken).ConfigureAwait(false); + + static async Task ComputeCacheAsync(Document document, CancellationToken cancellationToken) + { + var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + + // Find-Refs is not impacted by nullable types at all. So get a nullable-disabled semantic model to avoid + // unnecessary costs while binding. + var model = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + // It's very costly to walk an entire tree. So if the tree is simple and doesn't contain + // any unicode escapes in it, then we do simple string matching to find the tokens. + var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); - private readonly SemanticModel _semanticModel; + return new(document, text, model, root, index); + } + } + + public readonly Document Document; + public readonly SourceText Text; + public readonly SemanticModel SemanticModel; + public readonly SyntaxNode Root; + public readonly ISyntaxFactsService SyntaxFacts; + public readonly SyntaxTreeIndex SyntaxTreeIndex; private readonly ConcurrentDictionary _symbolInfoCache = []; private readonly ConcurrentDictionary> _identifierCache; @@ -32,9 +60,16 @@ public static FindReferenceCache GetCache(SemanticModel model) private ImmutableHashSet? _aliasNameSet; private ImmutableArray _constructorInitializerCache; - private FindReferenceCache(SemanticModel semanticModel) + private FindReferenceCache( + Document document, SourceText text, SemanticModel semanticModel, SyntaxNode root, SyntaxTreeIndex syntaxTreeIndex) { - _semanticModel = semanticModel; + Document = document; + Text = text; + SemanticModel = semanticModel; + Root = root; + SyntaxTreeIndex = syntaxTreeIndex; + SyntaxFacts = document.GetRequiredLanguageService(); + _identifierCache = new(comparer: semanticModel.Language switch { LanguageNames.VisualBasic => StringComparer.OrdinalIgnoreCase, @@ -44,29 +79,25 @@ private FindReferenceCache(SemanticModel semanticModel) } public SymbolInfo GetSymbolInfo(SyntaxNode node, CancellationToken cancellationToken) - { - return _symbolInfoCache.GetOrAdd(node, static (n, arg) => arg._semanticModel.GetSymbolInfo(n, arg.cancellationToken), (_semanticModel, cancellationToken)); - } + => _symbolInfoCache.GetOrAdd(node, static (n, arg) => arg.SemanticModel.GetSymbolInfo(n, arg.cancellationToken), (SemanticModel, cancellationToken)); public IAliasSymbol? GetAliasInfo( ISemanticFactsService semanticFacts, SyntaxToken token, CancellationToken cancellationToken) { if (_aliasNameSet == null) { - var set = semanticFacts.GetAliasNameSet(_semanticModel, cancellationToken); + var set = semanticFacts.GetAliasNameSet(SemanticModel, cancellationToken); Interlocked.CompareExchange(ref _aliasNameSet, set, null); } if (_aliasNameSet.Contains(token.ValueText)) - return _semanticModel.GetAliasInfo(token.GetRequiredParent(), cancellationToken); + return SemanticModel.GetAliasInfo(token.GetRequiredParent(), cancellationToken); return null; } - public async ValueTask> FindMatchingIdentifierTokensAsync( - Document document, - string identifier, - CancellationToken cancellationToken) + public ImmutableArray FindMatchingIdentifierTokens( + string identifier, CancellationToken cancellationToken) { if (identifier == "") { @@ -81,104 +112,82 @@ public async ValueTask> FindMatchingIdentifierTokens if (_identifierCache.TryGetValue(identifier, out var result)) return result; - // It's very costly to walk an entire tree. So if the tree is simple and doesn't contain - // any unicode escapes in it, then we do simple string matching to find the tokens. - var info = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); - // If this document doesn't even contain this identifier (escaped or non-escaped) we don't have to search it at all. - if (!info.ProbablyContainsIdentifier(identifier)) + if (!this.SyntaxTreeIndex.ProbablyContainsIdentifier(identifier)) return []; - return await ComputeAndCacheTokensAsync(this, document, identifier, info, cancellationToken).ConfigureAwait(false); + // If the identifier was escaped in the file then we'll have to do a more involved search that actually + // walks the root and checks all identifier tokens. + // + // otherwise, we can use the text of the document to quickly find candidates and test those directly. + return this.SyntaxTreeIndex.ProbablyContainsEscapedIdentifier(identifier) + ? _identifierCache.GetOrAdd(identifier, identifier => FindMatchingIdentifierTokensFromTree(identifier, cancellationToken)) + : _identifierCache.GetOrAdd(identifier, _ => FindMatchingIdentifierTokensFromText(identifier, cancellationToken)); + } - static async ValueTask> ComputeAndCacheTokensAsync( - FindReferenceCache cache, Document document, string identifier, SyntaxTreeIndex info, CancellationToken cancellationToken) + private bool IsMatch(string identifier, SyntaxToken token) + => !token.IsMissing && this.SyntaxFacts.IsIdentifier(token) && this.SyntaxFacts.TextMatch(token.ValueText, identifier); + + private ImmutableArray FindMatchingIdentifierTokensFromTree( + string identifier, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var result); + using var obj = SharedPools.Default>().GetPooledObject(); + + var stack = obj.Object; + stack.Push(this.Root); + + while (stack.TryPop(out var current)) { - var syntaxFacts = document.GetRequiredLanguageService(); - var root = await cache._semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - - // If the identifier was escaped in the file then we'll have to do a more involved search that actually - // walks the root and checks all identifier tokens. - // - // otherwise, we can use the text of the document to quickly find candidates and test those directly. - if (info.ProbablyContainsEscapedIdentifier(identifier)) + cancellationToken.ThrowIfCancellationRequested(); + if (current.IsNode) { - return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromTree(syntaxFacts, identifier, root)); + foreach (var child in current.AsNode()!.ChildNodesAndTokens().Reverse()) + stack.Push(child); } - else + else if (current.IsToken) { - var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromText(syntaxFacts, identifier, root, text, cancellationToken)); - } - } - - static bool IsMatch(ISyntaxFactsService syntaxFacts, string identifier, SyntaxToken token) - => !token.IsMissing && syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, identifier); - - static ImmutableArray FindMatchingIdentifierTokensFromTree( - ISyntaxFactsService syntaxFacts, string identifier, SyntaxNode root) - { - using var _ = ArrayBuilder.GetInstance(out var result); - using var obj = SharedPools.Default>().GetPooledObject(); - - var stack = obj.Object; - stack.Push(root); + var token = current.AsToken(); + if (IsMatch(identifier, token)) + result.Add(token); - while (stack.Count > 0) - { - var current = stack.Pop(); - if (current.IsNode) - { - foreach (var child in current.AsNode()!.ChildNodesAndTokens().Reverse()) - stack.Push(child); - } - else if (current.IsToken) + if (token.HasStructuredTrivia) { - var token = current.AsToken(); - if (IsMatch(syntaxFacts, identifier, token)) - result.Add(token); - - if (token.HasStructuredTrivia) + // structured trivia can only be leading trivia + foreach (var trivia in token.LeadingTrivia) { - // structured trivia can only be leading trivia - foreach (var trivia in token.LeadingTrivia) - { - if (trivia.HasStructure) - stack.Push(trivia.GetStructure()!); - } + if (trivia.HasStructure) + stack.Push(trivia.GetStructure()!); } } } - - return result.ToImmutableAndClear(); } - static ImmutableArray FindMatchingIdentifierTokensFromText( - ISyntaxFactsService syntaxFacts, string identifier, SyntaxNode root, SourceText sourceText, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var result); + return result.ToImmutableAndClear(); + } - var index = 0; - while ((index = sourceText.IndexOf(identifier, index, syntaxFacts.IsCaseSensitive)) >= 0) - { - cancellationToken.ThrowIfCancellationRequested(); + private ImmutableArray FindMatchingIdentifierTokensFromText( + string identifier, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var result); - var token = root.FindToken(index, findInsideTrivia: true); - var span = token.Span; - if (span.Start == index && span.Length == identifier.Length && IsMatch(syntaxFacts, identifier, token)) - result.Add(token); + var index = 0; + while ((index = this.Text.IndexOf(identifier, index, this.SyntaxFacts.IsCaseSensitive)) >= 0) + { + cancellationToken.ThrowIfCancellationRequested(); - var nextIndex = index + identifier.Length; - nextIndex = Math.Max(nextIndex, token.SpanStart); - index = nextIndex; - } + var token = this.Root.FindToken(index, findInsideTrivia: true); + var span = token.Span; + if (span.Start == index && span.Length == identifier.Length && IsMatch(identifier, token)) + result.Add(token); - return result.ToImmutable(); + var nextIndex = index + identifier.Length; + nextIndex = Math.Max(nextIndex, token.SpanStart); + index = nextIndex; } - } + return result.ToImmutableAndClear(); + } public IEnumerable GetConstructorInitializerTokens( ISyntaxFactsService syntaxFacts, SyntaxNode root, CancellationToken cancellationToken) { @@ -206,6 +215,6 @@ private static ImmutableArray GetConstructorInitializerTokensWorker } } - return initializers.ToImmutable(); + return initializers.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs index 59d513ef3bc73..7a2fb1f8c7d6a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs @@ -8,23 +8,25 @@ namespace Microsoft.CodeAnalysis.FindSymbols; -internal class FindReferencesDocumentState( - Document document, - SemanticModel semanticModel, - SyntaxNode root, +/// +/// Ephemeral information that find-references needs for a particular document when searching for a specific +/// symbol. Importantly, it contains the global aliases to that symbol within the current project. +/// +internal sealed class FindReferencesDocumentState( FindReferenceCache cache, HashSet? globalAliases) { private static readonly HashSet s_empty = []; - public readonly Document Document = document; - public readonly SemanticModel SemanticModel = semanticModel; - public readonly SyntaxNode Root = root; public readonly FindReferenceCache Cache = cache; public readonly HashSet GlobalAliases = globalAliases ?? s_empty; - public readonly Solution Solution = document.Project.Solution; - public readonly SyntaxTree SyntaxTree = semanticModel.SyntaxTree; - public readonly ISyntaxFactsService SyntaxFacts = document.GetRequiredLanguageService(); - public readonly ISemanticFactsService SemanticFacts = document.GetRequiredLanguageService(); + public Document Document => this.Cache.Document; + public SyntaxNode Root => this.Cache.Root; + public SemanticModel SemanticModel => this.Cache.SemanticModel; + public SyntaxTree SyntaxTree => this.SemanticModel.SyntaxTree; + + public Solution Solution => this.Document.Project.Solution; + public ISyntaxFactsService SyntaxFacts => this.Document.GetRequiredLanguageService(); + public ISemanticFactsService SemanticFacts => this.Document.GetRequiredLanguageService(); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs index a8ae4b1571f97..cf11c3110e238 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs @@ -6,7 +6,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols; @@ -42,20 +42,18 @@ public BidirectionalSymbolSet( } public override ImmutableArray GetAllSymbols() - => _allSymbols.ToImmutableArray(); + => [.. _allSymbols]; public override async Task InheritanceCascadeAsync(Project project, CancellationToken cancellationToken) { // Start searching using the current set of symbols built up so far. - var workQueue = new Stack(); - workQueue.Push(_allSymbols); + using var _ = ArrayBuilder.GetInstance(out var workQueue); + workQueue.AddRange(_allSymbols); var projects = ImmutableHashSet.Create(project); - while (workQueue.Count > 0) + while (workQueue.TryPop(out var current)) { - var current = workQueue.Pop(); - // For each symbol we're examining try to walk both up and down from it to see if we discover any // new symbols in this project. As long as we keep finding symbols, we'll keep searching from them // in both directions. diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs index a6778eb0608a7..2463a1d7033dc 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs @@ -16,7 +16,7 @@ internal partial class FindReferencesSearchEngine /// private sealed class NonCascadingSymbolSet(FindReferencesSearchEngine engine, MetadataUnifyingSymbolHashSet searchSymbols) : SymbolSet(engine) { - private readonly ImmutableArray _symbols = searchSymbols.ToImmutableArray(); + private readonly ImmutableArray _symbols = [.. searchSymbols]; public override ImmutableArray GetAllSymbols() => _symbols; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.SymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.SymbolSet.cs index 604cb09f37a3e..c2d404b6e9022 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.SymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.SymbolSet.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -148,18 +149,15 @@ public static async Task DetermineInitialSearchSy FindReferencesSearchEngine engine, MetadataUnifyingSymbolHashSet symbols, CancellationToken cancellationToken) { var result = new MetadataUnifyingSymbolHashSet(); - var workQueue = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var workQueue); // Start with the initial symbols we're searching for. foreach (var symbol in symbols) workQueue.Push(symbol); // As long as there's work in the queue, keep going. - while (workQueue.Count > 0) - { - var currentSymbol = workQueue.Pop(); + while (workQueue.TryPop(out var currentSymbol)) await AddCascadedAndLinkedSymbolsToAsync(engine, currentSymbol, result, workQueue, cancellationToken).ConfigureAwait(false); - } return result; } @@ -171,14 +169,13 @@ private static async Task DetermineInitialUpSymbo CancellationToken cancellationToken) { var upSymbols = new MetadataUnifyingSymbolHashSet(); - var workQueue = new Stack(); - workQueue.Push(initialSymbols); + using var _ = ArrayBuilder.GetInstance(out var workQueue); + workQueue.AddRange(initialSymbols); var solution = engine._solution; var allProjects = solution.Projects.ToImmutableHashSet(); - while (workQueue.Count > 0) + while (workQueue.TryPop(out var currentSymbol)) { - var currentSymbol = workQueue.Pop(); await AddUpSymbolsAsync( engine, currentSymbol, upSymbols, workQueue, allProjects, includeImplementationsThroughDerivedTypes, cancellationToken).ConfigureAwait(false); } @@ -187,14 +184,14 @@ await AddUpSymbolsAsync( } protected static async Task AddCascadedAndLinkedSymbolsToAsync( - FindReferencesSearchEngine engine, ImmutableArray symbols, MetadataUnifyingSymbolHashSet seenSymbols, Stack workQueue, CancellationToken cancellationToken) + FindReferencesSearchEngine engine, ImmutableArray symbols, MetadataUnifyingSymbolHashSet seenSymbols, ArrayBuilder workQueue, CancellationToken cancellationToken) { foreach (var symbol in symbols) await AddCascadedAndLinkedSymbolsToAsync(engine, symbol, seenSymbols, workQueue, cancellationToken).ConfigureAwait(false); } protected static async Task AddCascadedAndLinkedSymbolsToAsync( - FindReferencesSearchEngine engine, ISymbol symbol, MetadataUnifyingSymbolHashSet seenSymbols, Stack workQueue, CancellationToken cancellationToken) + FindReferencesSearchEngine engine, ISymbol symbol, MetadataUnifyingSymbolHashSet seenSymbols, ArrayBuilder workQueue, CancellationToken cancellationToken) { var solution = engine._solution; var mapped = await TryMapAndAddLinkedSymbolsAsync(symbol).ConfigureAwait(false); @@ -240,7 +237,7 @@ protected static async Task AddCascadedAndLinkedSymbolsToAsync( /// protected static async Task AddDownSymbolsAsync( FindReferencesSearchEngine engine, ISymbol symbol, - MetadataUnifyingSymbolHashSet seenSymbols, Stack workQueue, + MetadataUnifyingSymbolHashSet seenSymbols, ArrayBuilder workQueue, ImmutableHashSet projects, CancellationToken cancellationToken) { Contract.ThrowIfFalse(projects.Count == 1, "Only a single project should be passed in"); @@ -275,7 +272,7 @@ protected static async Task AddUpSymbolsAsync( FindReferencesSearchEngine engine, ISymbol symbol, MetadataUnifyingSymbolHashSet seenSymbols, - Stack workQueue, + ArrayBuilder workQueue, ImmutableHashSet projects, bool includeImplementationsThroughDerivedTypes, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs index 2e8ac46d238ac..9e15a2d7db461 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs @@ -40,24 +40,20 @@ public override ImmutableArray GetAllSymbols() var result = new MetadataUnifyingSymbolHashSet(); result.AddRange(_upSymbols); result.AddRange(initialSymbols); - return result.ToImmutableArray(); + return [.. result]; } public override async Task InheritanceCascadeAsync(Project project, CancellationToken cancellationToken) { // Start searching using the existing set of symbols found at the start (or anything found below that). - var workQueue = new Stack(); - workQueue.Push(initialSymbols); + using var _ = ArrayBuilder.GetInstance(out var workQueue); + workQueue.AddRange(initialSymbols); var projects = ImmutableHashSet.Create(project); - while (workQueue.Count > 0) - { - var current = workQueue.Pop(); - - // Keep adding symbols downwards in this project as long as we keep finding new symbols. + // Keep adding symbols downwards in this project as long as we keep finding new symbols. + while (workQueue.TryPop(out var current)) await AddDownSymbolsAsync(this.Engine, current, initialSymbols, workQueue, projects, cancellationToken).ConfigureAwait(false); - } } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index b29348e91c29c..1bd2f4943985b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -18,10 +19,10 @@ namespace Microsoft.CodeAnalysis.FindSymbols; +using Reference = (SymbolGroup group, ISymbol symbol, ReferenceLocation location); + internal partial class FindReferencesSearchEngine { - private static readonly ObjectPool s_metadataUnifyingSymbolHashSetPool = new(() => []); - private readonly Solution _solution; private readonly IImmutableSet? _documents; private readonly ImmutableArray _finders; @@ -29,11 +30,6 @@ internal partial class FindReferencesSearchEngine private readonly IStreamingFindReferencesProgress _progress; private readonly FindReferencesSearchOptions _options; - /// - /// Scheduler to run our tasks on. If we're in mode, we'll - /// run all our tasks concurrently. Otherwise, we will run them serially using - /// - private readonly TaskScheduler _scheduler; private static readonly TaskScheduler s_exclusiveScheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler; /// @@ -57,83 +53,108 @@ public FindReferencesSearchEngine( _options = options; _progressTracker = progress.ProgressTracker; - - // If we're an explicit invocation, just defer to the threadpool to execute all our work in parallel to get - // things done as quickly as possible. If we're running implicitly, then use a - // ConcurrentExclusiveSchedulerPair's exclusive scheduler as that's the most built-in way in the TPL to get - // will run things serially. - _scheduler = _options.Explicit ? TaskScheduler.Default : s_exclusiveScheduler; } + /// + /// Options to control the parallelism of the search. If we're in mode, we'll run all our tasks concurrently. Otherwise, we will + /// run them serially using + /// + private ParallelOptions GetParallelOptions(CancellationToken cancellationToken) + => new() + { + CancellationToken = cancellationToken, + // If we're an explicit invocation, just defer to the threadpool to execute all our work in parallel to get + // things done as quickly as possible. If we're running implicitly, then use a exclusive scheduler as + // that's the most built-in way in the TPL to get will run things serially. + TaskScheduler = _options.Explicit ? TaskScheduler.Default : s_exclusiveScheduler, + }; + public Task FindReferencesAsync(ISymbol symbol, CancellationToken cancellationToken) => FindReferencesAsync([symbol], cancellationToken); public async Task FindReferencesAsync( ImmutableArray symbols, CancellationToken cancellationToken) { - var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); - unifiedSymbols.AddRange(symbols); - await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); try { - var disposable = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposable.ConfigureAwait(false); + await ProducerConsumer.RunAsync( + ProducerConsumerOptions.SingleReaderOptions, + produceItems: static (onItemFound, args) => args.@this.PerformSearchAsync(args.symbols, onItemFound, args.cancellationToken), + consumeItems: static async (references, args) => await args.@this._progress.OnReferencesFoundAsync(references, @args.cancellationToken).ConfigureAwait(false), + (@this: this, symbols, cancellationToken), + cancellationToken).ConfigureAwait(false); + } + finally + { + await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + } + } - // Create the initial set of symbols to search for. As we walk the appropriate projects in the solution - // we'll expand this set as we discover new symbols to search for in each project. - var symbolSet = await SymbolSet.CreateAsync( - this, unifiedSymbols, includeImplementationsThroughDerivedTypes: true, cancellationToken).ConfigureAwait(false); + private async Task PerformSearchAsync( + ImmutableArray symbols, Action onReferenceFound, CancellationToken cancellationToken) + { + var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); + unifiedSymbols.AddRange(symbols); - // Report the initial set of symbols to the caller. - var allSymbols = symbolSet.GetAllSymbols(); - await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); + var disposable = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); + await using var _ = disposable.ConfigureAwait(false); - // Determine the set of projects we actually have to walk to find results in. If the caller provided a - // set of documents to search, we only bother with those. - var projectsToSearch = await GetProjectsToSearchAsync(allSymbols, cancellationToken).ConfigureAwait(false); + // Create the initial set of symbols to search for. As we walk the appropriate projects in the solution + // we'll expand this set as we discover new symbols to search for in each project. + var symbolSet = await SymbolSet.CreateAsync( + this, unifiedSymbols, includeImplementationsThroughDerivedTypes: true, cancellationToken).ConfigureAwait(false); - // We need to process projects in order when updating our symbol set. Say we have three projects (A, B - // and C), we cannot necessarily find inherited symbols in C until we have searched B. Importantly, - // while we're processing each project linearly to update the symbol set we're searching for, we still - // then process the projects in parallel once we know the set of symbols we're searching for in that - // project. - var dependencyGraph = _solution.GetProjectDependencyGraph(); - await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); + // Report the initial set of symbols to the caller. + var allSymbols = symbolSet.GetAllSymbols(); + await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); - using var _1 = ArrayBuilder.GetInstance(out var tasks); + // Determine the set of projects we actually have to walk to find results in. If the caller provided a + // set of documents to search, we only bother with those. + var projectsToSearch = await GetProjectsToSearchAsync(allSymbols, cancellationToken).ConfigureAwait(false); - foreach (var projectId in dependencyGraph.GetTopologicallySortedProjects(cancellationToken)) - { - var currentProject = _solution.GetRequiredProject(projectId); - if (!projectsToSearch.Contains(currentProject)) - continue; + await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); - // As we walk each project, attempt to grow the search set appropriately up and down the inheritance - // hierarchy and grab a copy of the symbols to be processed. Note: this has to happen serially - // which is why we do it in this loop and not inside the concurrent project processing that happens - // below. - await symbolSet.InheritanceCascadeAsync(currentProject, cancellationToken).ConfigureAwait(false); - allSymbols = symbolSet.GetAllSymbols(); + // Pull off and start searching each project as soon as we can once we've done the inheritance cascade into it. + await RoslynParallel.ForEachAsync( + GetProjectsAndSymbolsToSearchAsync(symbolSet, projectsToSearch, cancellationToken), + GetParallelOptions(cancellationToken), + async (tuple, cancellationToken) => await ProcessProjectAsync( + tuple.project, tuple.allSymbols, onReferenceFound, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); + } - // Report any new symbols we've cascaded to to our caller. - await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); + private async IAsyncEnumerable<(Project project, ImmutableArray allSymbols)> GetProjectsAndSymbolsToSearchAsync( + SymbolSet symbolSet, + ImmutableArray projectsToSearch, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + // We need to process projects in order when updating our symbol set. Say we have three projects (A, B + // and C), we cannot necessarily find inherited symbols in C until we have searched B. Importantly, + // while we're processing each project linearly to update the symbol set we're searching for, we still + // then process the projects in parallel once we know the set of symbols we're searching for in that + // project. + var dependencyGraph = _solution.GetProjectDependencyGraph(); + foreach (var projectId in dependencyGraph.GetTopologicallySortedProjects(cancellationToken)) + { + var currentProject = _solution.GetRequiredProject(projectId); + if (!projectsToSearch.Contains(currentProject)) + continue; + + // As we walk each project, attempt to grow the search set appropriately up and down the inheritance + // hierarchy and grab a copy of the symbols to be processed. Note: this has to happen serially + // which is why we do it in this loop and not inside the concurrent project processing that happens + // below. + await symbolSet.InheritanceCascadeAsync(currentProject, cancellationToken).ConfigureAwait(false); + var allSymbols = symbolSet.GetAllSymbols(); - tasks.Add(CreateWorkAsync(() => ProcessProjectAsync(currentProject, allSymbols, cancellationToken), cancellationToken)); - } + // Report any new symbols we've cascaded to to our caller. + await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); - // Now, wait for all projects to complete. - await Task.WhenAll(tasks).ConfigureAwait(false); - } - finally - { - await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + yield return (currentProject, allSymbols); } } - public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancellationToken) - => Task.Factory.StartNew(createWorkAsync, cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap(); - /// /// Notify the caller of the engine about the definitions we've found that we're looking for. We'll only notify /// them once per symbol group, but we may have to notify about new symbols each time we expand our symbol set @@ -184,12 +205,16 @@ private Task> GetProjectsToSearchAsync( return DependentProjectsFinder.GetDependentProjectsAsync(_solution, symbols, projects, cancellationToken); } - private async Task ProcessProjectAsync(Project project, ImmutableArray allSymbols, CancellationToken cancellationToken) + private async ValueTask ProcessProjectAsync( + Project project, ImmutableArray allSymbols, Action onReferenceFound, CancellationToken cancellationToken) { using var _1 = PooledDictionary>.GetInstance(out var symbolToGlobalAliases); using var _2 = PooledDictionary.GetInstance(out var documentToSymbols); try { + // scratch hashset to place results in. Populated/inspected/cleared in inner loop. + using var _3 = PooledHashSet.GetInstance(out var foundDocuments); + await AddGlobalAliasesAsync(project, allSymbols, symbolToGlobalAliases, cancellationToken).ConfigureAwait(false); foreach (var symbol in allSymbols) @@ -198,33 +223,29 @@ private async Task ProcessProjectAsync(Project project, ImmutableArray foreach (var finder in _finders) { - var documents = await finder.DetermineDocumentsToSearchAsync( - symbol, globalAliases, project, _documents, _options, cancellationToken).ConfigureAwait(false); - - foreach (var document in documents) - { - var docSymbols = GetSymbolSet(documentToSymbols, document); - docSymbols.Add(symbol); - } - } - } + await finder.DetermineDocumentsToSearchAsync( + symbol, globalAliases, project, _documents, + StandardCallbacks.AddToHashSet, + foundDocuments, + _options, cancellationToken).ConfigureAwait(false); - using var _3 = ArrayBuilder.GetInstance(out var tasks); - foreach (var (document, docSymbols) in documentToSymbols) - { - tasks.Add(CreateWorkAsync(() => ProcessDocumentAsync( - document, docSymbols, symbolToGlobalAliases, cancellationToken), cancellationToken)); + foreach (var document in foundDocuments) + GetSymbolSet(documentToSymbols, document).Add(symbol); + + foundDocuments.Clear(); + } } - await Task.WhenAll(tasks).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + documentToSymbols, + GetParallelOptions(cancellationToken), + (kvp, cancellationToken) => + ProcessDocumentAsync(kvp.Key, kvp.Value, symbolToGlobalAliases, onReferenceFound, cancellationToken)).ConfigureAwait(false); } finally { foreach (var (_, symbols) in documentToSymbols) - { - symbols.Clear(); - s_metadataUnifyingSymbolHashSetPool.Free(symbols); - } + MetadataUnifyingSymbolHashSet.ClearAndFree(symbols); FreeGlobalAliases(symbolToGlobalAliases); @@ -232,56 +253,57 @@ private async Task ProcessProjectAsync(Project project, ImmutableArray } static MetadataUnifyingSymbolHashSet GetSymbolSet(PooledDictionary dictionary, T key) where T : notnull - { - if (!dictionary.TryGetValue(key, out var set)) - { - set = s_metadataUnifyingSymbolHashSetPool.Allocate(); - dictionary.Add(key, set); - } - - return set; - } + => dictionary.GetOrAdd(key, static _ => MetadataUnifyingSymbolHashSet.AllocateFromPool()); } private static PooledHashSet? TryGet(Dictionary> dictionary, T key) where T : notnull => dictionary.TryGetValue(key, out var set) ? set : null; - private async Task ProcessDocumentAsync( + private async ValueTask ProcessDocumentAsync( Document document, MetadataUnifyingSymbolHashSet symbols, Dictionary> symbolToGlobalAliases, + Action onReferenceFound, CancellationToken cancellationToken) { - await _progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); - - try + // We're doing to do all of our processing of this document at once. This will necessitate all the + // appropriate finders checking this document for hits. We know that in the initial pass to determine + // documents, this document was already considered a strong match (e.g. we know it contains the name of + // the symbol being searched for). As such, we're almost certainly going to have to do semantic checks + // to now see if the candidate actually matches the symbol. This will require syntax and semantics. So + // just grab those once here and hold onto them for the lifetime of this call. + var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); + + // This search almost always involves trying to find the tokens matching the name of the symbol we're looking + // for. Get the cache ready with those tokens so that kicking of N searches to search for each symbol in + // parallel doesn't cause us to compute and cache the same thing concurrently. + + // Note: cascaded symbols will normally have the same name. That's ok. The second call to + // FindMatchingIdentifierTokens with the same name will short circuit since it will already see the result of + // the prior call. + foreach (var symbol in symbols) { - // We're doing to do all of our processing of this document at once. This will necessitate all the - // appropriate finders checking this document for hits. We know that in the initial pass to determine - // documents, this document was already considered a strong match (e.g. we know it contains the name of - // the symbol being searched for). As such, we're almost certainly going to have to do semantic checks - // to now see if the candidate actually matches the symbol. This will require syntax and semantics. So - // just grab those once here and hold onto them for the lifetime of this call. - var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var cache = FindReferenceCache.GetCache(model); - - foreach (var symbol in symbols) + if (symbol.CanBeReferencedByName) + cache.FindMatchingIdentifierTokens(symbol.Name, cancellationToken); + } + + await RoslynParallel.ForEachAsync( + symbols, + GetParallelOptions(cancellationToken), + async (symbol, cancellationToken) => { - var globalAliases = TryGet(symbolToGlobalAliases, symbol); + // symbolToGlobalAliases is safe to read in parallel. It is created fully before this point and is no + // longer mutated. var state = new FindReferencesDocumentState( - document, model, root, cache, globalAliases); - await ProcessDocumentAsync(symbol, state).ConfigureAwait(false); - } - } - finally - { - await _progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); - } + cache, TryGet(symbolToGlobalAliases, symbol)); + + await ProcessDocumentAsync(symbol, state, onReferenceFound).ConfigureAwait(false); + }).ConfigureAwait(false); + + return; async Task ProcessDocumentAsync( - ISymbol symbol, - FindReferencesDocumentState state) + ISymbol symbol, FindReferencesDocumentState state, Action onReferenceFound) { cancellationToken.ThrowIfCancellationRequested(); @@ -290,12 +312,18 @@ async Task ProcessDocumentAsync( // This is safe to just blindly read. We can only ever get here after the call to ReportGroupsAsync // happened. So there must be a group for this symbol in our map. var group = _symbolToGroup[symbol]; + + // Note: nearly every finder will no-op when passed a in a symbol it's not applicable to. So it's + // simple to just iterate over all of them, knowing that will quickly skip all the irrelevant ones, + // and only do interesting work on the single relevant one. foreach (var finder in _finders) { - var references = await finder.FindReferencesInDocumentAsync( - symbol, state, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in references) - await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); + await finder.FindReferencesInDocumentAsync( + symbol, state, + static (loc, tuple) => tuple.onReferenceFound((tuple.group, tuple.symbol, loc.Location)), + (group, symbol, onReferenceFound), + _options, + cancellationToken).ConfigureAwait(false); } } } @@ -314,24 +342,13 @@ private async Task AddGlobalAliasesAsync( var aliases = await finder.DetermineGlobalAliasesAsync( symbol, project, cancellationToken).ConfigureAwait(false); if (aliases.Length > 0) - { - var globalAliases = GetGlobalAliasesSet(symbolToGlobalAliases, symbol); - globalAliases.AddRange(aliases); - } + GetGlobalAliasesSet(symbolToGlobalAliases, symbol).AddRange(aliases); } } } private static PooledHashSet GetGlobalAliasesSet(PooledDictionary> dictionary, T key) where T : notnull - { - if (!dictionary.TryGetValue(key, out var set)) - { - set = PooledHashSet.GetInstance(); - dictionary.Add(key, set); - } - - return set; - } + => dictionary.GetOrAdd(key, static _ => PooledHashSet.GetInstance()); private static void FreeGlobalAliases(PooledDictionary> symbolToGlobalAliases) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 6c5dc334823e7..0666734493488 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -87,41 +87,50 @@ async ValueTask PerformSearchInDocumentAsync( // appropriate finders checking this document for hits. We're likely going to need to perform syntax // and semantics checks in this file. So just grab those once here and hold onto them for the lifetime // of this call. - var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var cache = FindReferenceCache.GetCache(model); + var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); foreach (var symbol in symbols) { - var globalAliases = GetGlobalAliasesSet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(document, model, root, cache, globalAliases); + var state = new FindReferencesDocumentState( + cache, TryGet(symbolToGlobalAliases, symbol)); - await PerformSearchInDocumentWorkerAsync(symbol, document, state).ConfigureAwait(false); + await PerformSearchInDocumentWorkerAsync(symbol, state).ConfigureAwait(false); } } - async ValueTask PerformSearchInDocumentWorkerAsync( - ISymbol symbol, Document document, FindReferencesDocumentState state) + async ValueTask PerformSearchInDocumentWorkerAsync(ISymbol symbol, FindReferencesDocumentState state) { // Always perform a normal search, looking for direct references to exactly that symbol. + await DirectSymbolSearchAsync(symbol, state).ConfigureAwait(false); + + // Now, for symbols that could involve inheritance, look for references to the same named entity, and + // see if it's a reference to a symbol that shares an inheritance relationship with that symbol. + await InheritanceSymbolSearchAsync(symbol, state).ConfigureAwait(false); + } + + async ValueTask DirectSymbolSearchAsync(ISymbol symbol, FindReferencesDocumentState state) + { + using var _ = ArrayBuilder.GetInstance(out var referencesForFinder); foreach (var finder in _finders) { - var references = await finder.FindReferencesInDocumentAsync( - symbol, state, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in references) - { - var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); - await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); - } + await finder.FindReferencesInDocumentAsync( + symbol, state, StandardCallbacks.AddToArrayBuilder, referencesForFinder, _options, cancellationToken).ConfigureAwait(false); } - // Now, for symbols that could involve inheritance, look for references to the same named entity, and - // see if it's a reference to a symbol that shares an inheritance relationship with that symbol. + if (referencesForFinder.Count > 0) + { + var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); + var references = referencesForFinder.SelectAsArray(r => (group, symbol, r.Location)); + await _progress.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false); + } + } + + async ValueTask InheritanceSymbolSearchAsync(ISymbol symbol, FindReferencesDocumentState state) + { if (InvolvesInheritance(symbol)) { - var tokens = await AbstractReferenceFinder.FindMatchingIdentifierTokensAsync( - state, symbol.Name, cancellationToken).ConfigureAwait(false); + var tokens = AbstractReferenceFinder.FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); foreach (var token in tokens) { @@ -135,7 +144,7 @@ async ValueTask PerformSearchInDocumentWorkerAsync( var candidateGroup = await ReportGroupAsync(candidate, cancellationToken).ConfigureAwait(false); var location = AbstractReferenceFinder.CreateReferenceLocation(state, token, candidateReason, cancellationToken); - await _progress.OnReferenceFoundAsync(candidateGroup, candidate, location, cancellationToken).ConfigureAwait(false); + await _progress.OnReferencesFoundAsync([(candidateGroup, candidate, location)], cancellationToken).ConfigureAwait(false); } } } @@ -177,7 +186,7 @@ async Task ComputeInheritanceRelationshipAsync( // Counter-intuitive, but if these are matching symbols, they do *not* have an inheritance relationship. // We do *not* want to report these as they would have been found in the original call to the finders in // PerformSearchInTextSpanAsync. - if (await SymbolFinder.OriginalSymbolsMatchAsync(_solution, searchSymbol, candidate, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(_solution, searchSymbol, candidate)) return false; // walk up the original symbol's inheritance hierarchy to see if we hit the candidate. Don't walk down @@ -186,7 +195,7 @@ async Task ComputeInheritanceRelationshipAsync( this, [searchSymbol], includeImplementationsThroughDerivedTypes: false, cancellationToken).ConfigureAwait(false); foreach (var symbolUp in searchSymbolUpSet.GetAllSymbols()) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(_solution, symbolUp, candidate, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(_solution, symbolUp, candidate)) return true; } @@ -196,7 +205,7 @@ async Task ComputeInheritanceRelationshipAsync( this, [candidate], includeImplementationsThroughDerivedTypes: false, cancellationToken).ConfigureAwait(false); foreach (var candidateUp in candidateSymbolUpSet.GetAllSymbols()) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(_solution, searchSymbol, candidateUp, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(_solution, searchSymbol, candidateUp)) return true; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchOptions.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchOptions.cs index 6a949418c1c15..900771145172e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchOptions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchOptions.cs @@ -53,13 +53,18 @@ namespace Microsoft.CodeAnalysis.FindSymbols; /// option is the more relevant with knowing if a particular reference would actually result in a call to the /// original member, not if it has a relation to the original member. /// +/// +/// Displays all definitions regardless of whether they have a reference or not. +/// /// + [DataContract] internal readonly record struct FindReferencesSearchOptions( [property: DataMember(Order = 0)] bool AssociatePropertyReferencesWithSpecificAccessor = false, [property: DataMember(Order = 1)] bool Cascade = true, [property: DataMember(Order = 2)] bool Explicit = true, - [property: DataMember(Order = 3)] bool UnidirectionalHierarchyCascade = false) + [property: DataMember(Order = 3)] bool UnidirectionalHierarchyCascade = false, + [property: DataMember(Order = 4)] bool DisplayAllDefinitions = false) { public FindReferencesSearchOptions() : this(AssociatePropertyReferencesWithSpecificAccessor: false) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 224ab915fc901..bc902b6dde014 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -23,45 +24,51 @@ protected abstract bool TokensMatch( protected sealed override bool CanFind(TSymbol symbol) => true; - protected sealed override Task> DetermineDocumentsToSearchAsync( + protected sealed override Task DetermineDocumentsToSearchAsync( TSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var location = symbol.Locations.FirstOrDefault(); if (location == null || !location.IsInSource) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; var document = project.GetDocument(location.SourceTree); if (document == null) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; if (documents != null && !documents.Contains(document)) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; - return Task.FromResult(ImmutableArray.Create(document)); + processResult(document, processResultData); + return Task.CompletedTask; } - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( TSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var container = GetContainer(symbol); if (container != null) - return await FindReferencesInContainerAsync(symbol, container, state, cancellationToken).ConfigureAwait(false); - - if (symbol.ContainingType != null && symbol.ContainingType.IsScriptClass) { - var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); - return await FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken).ConfigureAwait(false); + FindReferencesInContainer(symbol, container, state, processResult, processResultData, cancellationToken); + } + else if (symbol.ContainingType != null && symbol.ContainingType.IsScriptClass) + { + var tokens = FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } - return []; + return ValueTaskFactory.CompletedTask; } private static ISymbol? GetContainer(ISymbol symbol) @@ -92,10 +99,12 @@ protected sealed override async ValueTask> FindRe return null; } - private ValueTask> FindReferencesInContainerAsync( + private void FindReferencesInContainer( TSymbol symbol, ISymbol container, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var service = state.Document.GetRequiredLanguageService(); @@ -114,6 +123,7 @@ private ValueTask> FindReferencesInContainerAsync } } - return FindReferencesInTokensAsync(symbol, state, tokens.ToImmutable(), cancellationToken); + FindReferencesInTokens( + symbol, state, tokens.ToImmutable(), processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs index 706ea16052e72..e9944e7ff30c0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs @@ -4,10 +4,8 @@ using System.Collections.Immutable; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -41,7 +39,7 @@ protected static ImmutableArray GetReferencedAccessorSymbols( if (!semanticFacts.IsOnlyWrittenTo(semanticModel, node, cancellationToken)) result.AddIfNotNull(property.GetMethod); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } else { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index c0ac6f521648d..aeb052995d490 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -30,13 +30,13 @@ public abstract Task> DetermineGlobalAliasesAsync( public abstract ValueTask> DetermineCascadedSymbolsAsync( ISymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken); - public abstract Task> DetermineDocumentsToSearchAsync( - ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken); + public abstract Task DetermineDocumentsToSearchAsync( + ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); - public abstract ValueTask> FindReferencesInDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken); + public abstract ValueTask FindReferencesInDocumentAsync( + ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); - private static ValueTask<(bool matched, CandidateReason reason)> SymbolsMatchAsync( + private static (bool matched, CandidateReason reason) SymbolsMatch( ISymbol symbol, FindReferencesDocumentState state, SyntaxToken token, CancellationToken cancellationToken) { // delegates don't have exposed symbols for their constructors. so when you do `new MyDel()`, that's only a @@ -47,26 +47,25 @@ public abstract ValueTask> FindReferencesInDocume : state.SyntaxFacts.TryGetBindableParent(token); parent ??= token.Parent!; - return SymbolsMatchAsync(symbol, state, parent, cancellationToken); + return SymbolsMatch(symbol, state, parent, cancellationToken); } - protected static ValueTask<(bool matched, CandidateReason reason)> SymbolsMatchAsync( + protected static (bool matched, CandidateReason reason) SymbolsMatch( ISymbol searchSymbol, FindReferencesDocumentState state, SyntaxNode node, CancellationToken cancellationToken) { var symbolInfo = state.Cache.GetSymbolInfo(node, cancellationToken); - - return MatchesAsync(searchSymbol, state, symbolInfo, cancellationToken); + return Matches(searchSymbol, state, symbolInfo); } - protected static async ValueTask<(bool matched, CandidateReason reason)> MatchesAsync( - ISymbol searchSymbol, FindReferencesDocumentState state, SymbolInfo symbolInfo, CancellationToken cancellationToken) + protected static (bool matched, CandidateReason reason) Matches( + ISymbol searchSymbol, FindReferencesDocumentState state, SymbolInfo symbolInfo) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(state.Solution, searchSymbol, symbolInfo.Symbol, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(state.Solution, searchSymbol, symbolInfo.Symbol)) return (matched: true, CandidateReason.None); foreach (var candidate in symbolInfo.CandidateSymbols) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(state.Solution, searchSymbol, candidate, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(state.Solution, searchSymbol, candidate)) return (matched: true, symbolInfo.CandidateReason); } @@ -81,11 +80,13 @@ protected static bool TryGetNameWithoutAttributeSuffix( return name.TryGetWithoutAttributeSuffix(syntaxFacts.IsCaseSensitive, out result); } - protected static async Task> FindDocumentsAsync( + protected static async Task FindDocumentsAsync( Project project, IImmutableSet? scope, Func> predicateAsync, T value, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { // special case for highlight references @@ -93,31 +94,30 @@ protected static async Task> FindDocumentsAsync( { var document = scope.First(); if (document.Project == project) - return scope.ToImmutableArray(); + processResult(document, processResultData); - return []; + return; } - using var _ = ArrayBuilder.GetInstance(out var documents); foreach (var document in await project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)) { if (scope != null && !scope.Contains(document)) continue; if (await predicateAsync(document, value, cancellationToken).ConfigureAwait(false)) - documents.Add(document); + processResult(document, processResultData); } - - return documents.ToImmutable(); } /// /// Finds all the documents in the provided project that contain the requested string /// values /// - protected static Task> FindDocumentsAsync( + protected static Task FindDocumentsAsync( Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, CancellationToken cancellationToken, params string[] values) { @@ -130,74 +130,74 @@ protected static Task> FindDocumentsAsync( } return true; - }, values, cancellationToken); + }, values, processResult, processResultData, cancellationToken); } /// /// Finds all the documents in the provided project that contain a global attribute in them. /// - protected static Task> FindDocumentsWithGlobalSuppressMessageAttributeAsync( - Project project, IImmutableSet? documents, CancellationToken cancellationToken) + protected static Task FindDocumentsWithGlobalSuppressMessageAttributeAsync( + Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( - project, documents, static index => index.ContainsGlobalSuppressMessageAttribute, cancellationToken); + project, documents, static index => index.ContainsGlobalSuppressMessageAttribute, processResult, processResultData, cancellationToken); } - protected static Task> FindDocumentsAsync( + protected static Task FindDocumentsAsync( Project project, IImmutableSet? documents, PredefinedType predefinedType, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (predefinedType == PredefinedType.None) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; return FindDocumentsWithPredicateAsync( - project, documents, static (index, predefinedType) => index.ContainsPredefinedType(predefinedType), predefinedType, cancellationToken); + project, documents, static (index, predefinedType) => index.ContainsPredefinedType(predefinedType), predefinedType, processResult, processResultData, cancellationToken); } protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string name, SyntaxToken token) => syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, name); - [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - protected static async ValueTask> FindReferencesInDocumentUsingIdentifierAsync( + protected static void FindReferencesInDocumentUsingIdentifier( ISymbol symbol, string identifier, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - var tokens = await FindMatchingIdentifierTokensAsync(state, identifier, cancellationToken).ConfigureAwait(false); - return await FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, identifier, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } - public static ValueTask> FindMatchingIdentifierTokensAsync(FindReferencesDocumentState state, string identifier, CancellationToken cancellationToken) - => state.Cache.FindMatchingIdentifierTokensAsync(state.Document, identifier, cancellationToken); + public static ImmutableArray FindMatchingIdentifierTokens(FindReferencesDocumentState state, string identifier, CancellationToken cancellationToken) + => state.Cache.FindMatchingIdentifierTokens(identifier, cancellationToken); - protected static async ValueTask> FindReferencesInTokensAsync( + protected static void FindReferencesInTokens( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray tokens, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (tokens.IsEmpty) - return []; + return; - using var _ = ArrayBuilder.GetInstance(out var locations); foreach (var token in tokens) { cancellationToken.ThrowIfCancellationRequested(); - var (matched, reason) = await SymbolsMatchAsync( - symbol, state, token, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, token, cancellationToken); if (matched) { var finderLocation = CreateFinderLocation(state, token, reason, cancellationToken); - - locations.Add(finderLocation); + processResult(finderLocation, processResultData); } } - - return locations.ToImmutable(); } protected static FinderLocation CreateFinderLocation(FindReferencesDocumentState state, SyntaxToken token, CandidateReason reason, CancellationToken cancellationToken) @@ -239,27 +239,29 @@ public static ReferenceLocation CreateReferenceLocation(FindReferencesDocumentSt return null; } - protected static async Task> FindLocalAliasReferencesAsync( + protected static void FindLocalAliasReferences( ArrayBuilder initialReferences, ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); - return aliasSymbols.IsDefaultOrEmpty - ? [] - : await FindReferencesThroughLocalAliasSymbolsAsync(symbol, state, aliasSymbols, cancellationToken).ConfigureAwait(false); + if (!aliasSymbols.IsDefaultOrEmpty) + FindReferencesThroughLocalAliasSymbols(symbol, state, aliasSymbols, processResult, processResultData, cancellationToken); } - protected static async Task> FindLocalAliasReferencesAsync( + protected static void FindLocalAliasReferences( ArrayBuilder initialReferences, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); - return aliasSymbols.IsDefaultOrEmpty - ? [] - : await FindReferencesThroughLocalAliasSymbolsAsync(state, aliasSymbols, cancellationToken).ConfigureAwait(false); + if (!aliasSymbols.IsDefaultOrEmpty) + FindReferencesThroughLocalAliasSymbols(state, aliasSymbols, processResult, processResultData, cancellationToken); } private static ImmutableArray GetLocalAliasSymbols( @@ -278,127 +280,127 @@ private static ImmutableArray GetLocalAliasSymbols( return aliasSymbols.ToImmutableAndClear(); } - private static async Task> FindReferencesThroughLocalAliasSymbolsAsync( + private static void FindReferencesThroughLocalAliasSymbols( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray localAliasSymbols, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var allAliasReferences); foreach (var localAliasSymbol in localAliasSymbols) { - var aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - symbol, localAliasSymbol.Name, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + FindReferencesInDocumentUsingIdentifier( + symbol, localAliasSymbol.Name, state, processResult, processResultData, cancellationToken); + // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(localAliasSymbol.Name, state.SyntaxFacts, out var simpleName)) { - aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - symbol, simpleName, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + FindReferencesInDocumentUsingIdentifier( + symbol, simpleName, state, processResult, processResultData, cancellationToken); } } - - return allAliasReferences.ToImmutable(); } - private static async Task> FindReferencesThroughLocalAliasSymbolsAsync( + private static void FindReferencesThroughLocalAliasSymbols( FindReferencesDocumentState state, ImmutableArray localAliasSymbols, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var allAliasReferences); foreach (var aliasSymbol in localAliasSymbols) { - var aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, aliasSymbol.Name, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + FindReferencesInDocumentUsingIdentifier( + aliasSymbol, aliasSymbol.Name, state, processResult, processResultData, cancellationToken); + // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(aliasSymbol.Name, state.SyntaxFacts, out var simpleName)) { - aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, simpleName, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + FindReferencesInDocumentUsingIdentifier( + aliasSymbol, simpleName, state, processResult, processResultData, cancellationToken); } } - - return allAliasReferences.ToImmutable(); } - protected static Task> FindDocumentsWithPredicateAsync( + protected static Task FindDocumentsWithPredicateAsync( Project project, IImmutableSet? documents, Func predicate, T value, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return FindDocumentsAsync(project, documents, static async (d, t, c) => { var info = await SyntaxTreeIndex.GetRequiredIndexAsync(d, c).ConfigureAwait(false); return t.predicate(info, t.value); - }, (predicate, value), cancellationToken); + }, (predicate, value), processResult, processResultData, cancellationToken); } - protected static Task> FindDocumentsWithPredicateAsync( + protected static Task FindDocumentsWithPredicateAsync( Project project, IImmutableSet? documents, Func predicate, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( project, documents, static (info, predicate) => predicate(info), predicate, + processResult, + processResultData, cancellationToken); } - protected static Task> FindDocumentsWithForEachStatementsAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsForEachStatement, cancellationToken); + protected static Task FindDocumentsWithForEachStatementsAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsForEachStatement, processResult, processResultData, cancellationToken); /// /// If the `node` implicitly matches the `symbol`, then it will be added to `locations`. /// - protected delegate void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations); + protected delegate void CollectMatchingReferences( + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData); - protected static async Task> FindReferencesInDocumentAsync( + protected static void FindReferencesInDocument( FindReferencesDocumentState state, Func isRelevantDocument, - CollectMatchingReferences collectMatchingReferences, + CollectMatchingReferences collectMatchingReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - var document = state.Document; - var syntaxTreeInfo = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); + var syntaxTreeInfo = state.Cache.SyntaxTreeIndex; if (isRelevantDocument(syntaxTreeInfo)) { - using var _ = ArrayBuilder.GetInstance(out var locations); - foreach (var node in state.Root.DescendantNodesAndSelf()) { cancellationToken.ThrowIfCancellationRequested(); - collectMatchingReferences(node, state, locations); + collectMatchingReferences(node, state, processResult, processResultData); } - - return locations.ToImmutable(); } - - return []; } - protected Task> FindReferencesInForEachStatementsAsync( + protected void FindReferencesInForEachStatements( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsForEachStatement; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var info = state.SemanticFacts.GetForEachSymbols(state.SemanticModel, node); @@ -410,30 +412,34 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location: location, isImplicit: true, symbolUsageInfo, GetAdditionalFindUsagesProperties(node, state), - candidateReason: CandidateReason.None))); + candidateReason: CandidateReason.None)); + processResult(result, processResultData); } } } - protected Task> FindReferencesInCollectionInitializerAsync( + protected void FindReferencesInCollectionInitializer( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsCollectionInitializer; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { if (!state.SyntaxFacts.IsObjectCollectionInitializer(node)) return; @@ -448,31 +454,35 @@ void CollectMatchingReferences( var location = expression.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(expression, state, cancellationToken); - locations.Add(new FinderLocation(expression, new ReferenceLocation( + var result = new FinderLocation(expression, new ReferenceLocation( state.Document, alias: null, location: location, isImplicit: true, symbolUsageInfo, GetAdditionalFindUsagesProperties(expression, state), - candidateReason: CandidateReason.None))); + candidateReason: CandidateReason.None)); + processResult(result, processResultData); } } } } - protected Task> FindReferencesInDeconstructionAsync( + protected void FindReferencesInDeconstruction( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsDeconstruction; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var semanticModel = state.SemanticModel; var semanticFacts = state.SemanticFacts; @@ -488,25 +498,29 @@ void CollectMatchingReferences( var location = state.SyntaxFacts.GetDeconstructionReferenceLocation(node); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } - protected Task> FindReferencesInAwaitExpressionAsync( + protected void FindReferencesInAwaitExpression( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsAwait; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var awaitExpressionMethod = state.SemanticFacts.GetGetAwaiterMethod(state.SemanticModel, node); @@ -515,25 +529,29 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } - protected Task> FindReferencesInImplicitObjectCreationExpressionAsync( + protected void FindReferencesInImplicitObjectCreationExpression( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { // Avoid binding unrelated nodes if (!state.SyntaxFacts.IsImplicitObjectCreationExpression(node)) @@ -546,9 +564,10 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } @@ -660,9 +679,9 @@ SymbolUsageInfo GetSymbolUsageInfoCommon() var operation = semanticModel.GetOperation(node, cancellationToken); switch (operation?.Parent) { - case INameOfOperation _: - case ITypeOfOperation _: - case ISizeOfOperation _: + case INameOfOperation: + case ITypeOfOperation: + case ISizeOfOperation: return SymbolUsageInfo.Create(ValueUsageInfo.Name); } @@ -814,7 +833,7 @@ private static bool TryGetAdditionalProperty(string propertyName, ISymbol symbol return false; } - additionalProperty = new KeyValuePair(propertyName, symbol.Name); + additionalProperty = KeyValuePairUtil.Create(propertyName, symbol.Name); return true; } } @@ -824,12 +843,15 @@ internal abstract partial class AbstractReferenceFinder : AbstractRefer { protected abstract bool CanFind(TSymbol symbol); - protected abstract Task> DetermineDocumentsToSearchAsync( + protected abstract Task DetermineDocumentsToSearchAsync( TSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); - protected abstract ValueTask> FindReferencesInDocumentAsync( - TSymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken); + protected abstract ValueTask FindReferencesInDocumentAsync( + TSymbol symbol, FindReferencesDocumentState state, + Action processResult, TData processResultData, + FindReferencesSearchOptions options, CancellationToken cancellationToken); protected virtual Task> DetermineGlobalAliasesAsync( TSymbol symbol, Project project, CancellationToken cancellationToken) @@ -845,21 +867,23 @@ public sealed override Task> DetermineGlobalAliasesAsync( : SpecializedTasks.EmptyImmutableArray(); } - public sealed override Task> DetermineDocumentsToSearchAsync( + public sealed override Task DetermineDocumentsToSearchAsync( ISymbol symbol, HashSet? globalAliases, Project project, - IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) + IImmutableSet? documents, Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return symbol is TSymbol typedSymbol && CanFind(typedSymbol) - ? DetermineDocumentsToSearchAsync(typedSymbol, globalAliases, project, documents, options, cancellationToken) - : SpecializedTasks.EmptyImmutableArray(); + if (symbol is TSymbol typedSymbol && CanFind(typedSymbol)) + return DetermineDocumentsToSearchAsync(typedSymbol, globalAliases, project, documents, processResult, processResultData, options, cancellationToken); + + return Task.CompletedTask; } - public sealed override ValueTask> FindReferencesInDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken) + public sealed override ValueTask FindReferencesInDocumentAsync( + ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { return symbol is TSymbol typedSymbol && CanFind(typedSymbol) - ? FindReferencesInDocumentAsync(typedSymbol, state, options, cancellationToken) - : new ValueTask>([]); + ? FindReferencesInDocumentAsync(typedSymbol, state, processResult, processResultData, options, cancellationToken) + : ValueTaskFactory.CompletedTask; } public sealed override ValueTask> DetermineCascadedSymbolsAsync( @@ -881,11 +905,11 @@ protected virtual ValueTask> DetermineCascadedSymbolsAsy return new([]); } - protected static ValueTask> FindReferencesInDocumentUsingSymbolNameAsync( - TSymbol symbol, FindReferencesDocumentState state, CancellationToken cancellationToken) + protected static void FindReferencesInDocumentUsingSymbolName( + TSymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync( - symbol, symbol.Name, state, cancellationToken); + FindReferencesInDocumentUsingIdentifier( + symbol, symbol.Name, state, processResult, processResultData, cancellationToken); } protected static async Task> GetAllMatchingGlobalAliasNamesAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs index 2202d3b2dc838..e03b3805db4a8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -51,46 +50,47 @@ static bool SupportsGlobalSuppression(ISymbol symbol) /// [assembly: SuppressMessage("RuleCategory", "RuleId', Scope = "member", Target = "~F:C.Field")] /// [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - protected static async ValueTask> FindReferencesInDocumentInsideGlobalSuppressionsAsync( + protected static void FindReferencesInDocumentInsideGlobalSuppressions( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (!ShouldFindReferencesInGlobalSuppressions(symbol, out var docCommentId)) - return []; + return; // Check if we have any relevant global attributes in this document. - var info = await SyntaxTreeIndex.GetRequiredIndexAsync(state.Document, cancellationToken).ConfigureAwait(false); + var info = state.Cache.SyntaxTreeIndex; if (!info.ContainsGlobalSuppressMessageAttribute) - return []; + return; var semanticModel = state.SemanticModel; var suppressMessageAttribute = semanticModel.Compilation.SuppressMessageAttributeType(); if (suppressMessageAttribute == null) - return []; + return; // Check if we have any instances of the symbol documentation comment ID string literals within global attributes. // These string literals represent references to the symbol. if (!TryGetExpectedDocumentationCommentId(docCommentId, out var expectedDocCommentId)) - return []; + return; var syntaxFacts = state.SyntaxFacts; // We map the positions of documentation ID literals in tree to string literal tokens, // perform semantic checks to ensure these are valid references to the symbol // and if so, add these locations to the computed references. - var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var locations); + var root = state.Root; foreach (var token in root.DescendantTokens()) { if (IsCandidate(state, token, expectedDocCommentId.Span, suppressMessageAttribute, cancellationToken, out var offsetOfReferenceInToken)) { var referenceLocation = CreateReferenceLocation(offsetOfReferenceInToken, token, root, state.Document, syntaxFacts); - locations.Add(new FinderLocation(token.GetRequiredParent(), referenceLocation)); + processResult(new FinderLocation(token.GetRequiredParent(), referenceLocation), processResultData); } } - return locations.ToImmutable(); + return; // Local functions static bool IsCandidate( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs index caeeabeb0aa83..b84e1bd7abaf1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs @@ -7,16 +7,17 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; internal abstract class AbstractTypeParameterSymbolReferenceFinder : AbstractReferenceFinder { - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( ITypeParameterSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -28,17 +29,21 @@ protected sealed override async ValueTask> FindRe // T()`). In the former case GetSymbolInfo can be used to bind the symbol and check if it matches this symbol. // in the latter though GetSymbolInfo will fail and we have to directly check if we have the right type info. - var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); - var normalReferences = await FindReferencesInTokensAsync( + FindReferencesInTokens( symbol, state, tokens.WhereAsArray(static (token, state) => !IsObjectCreationToken(token, state), state), - cancellationToken).ConfigureAwait(false); + processResult, + processResultData, + cancellationToken); - var objectCreationReferences = GetObjectCreationReferences( - tokens.WhereAsArray(static (token, state) => IsObjectCreationToken(token, state), state)); + GetObjectCreationReferences( + tokens.WhereAsArray(static (token, state) => IsObjectCreationToken(token, state), state), + processResult, + processResultData); - return normalReferences.Concat(objectCreationReferences); + return ValueTaskFactory.CompletedTask; static bool IsObjectCreationToken(SyntaxToken token, FindReferencesDocumentState state) { @@ -47,19 +52,18 @@ static bool IsObjectCreationToken(SyntaxToken token, FindReferencesDocumentState syntaxFacts.IsObjectCreationExpression(token.Parent.Parent); } - ImmutableArray GetObjectCreationReferences(ImmutableArray objectCreationTokens) + void GetObjectCreationReferences( + ImmutableArray objectCreationTokens, + Action processResult, + TData processResultData) { - using var _ = ArrayBuilder.GetInstance(out var result); - foreach (var token in objectCreationTokens) { Contract.ThrowIfNull(token.Parent?.Parent); var typeInfo = state.SemanticModel.GetTypeInfo(token.Parent.Parent, cancellationToken); if (symbol.Equals(typeInfo.Type, SymbolEqualityComparer.Default)) - result.Add(CreateFinderLocation(state, token, CandidateReason.None, cancellationToken)); + processResult(CreateFinderLocation(state, token, CandidateReason.None, cancellationToken), processResultData); } - - return result.ToImmutable(); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index 88dc9fad96af2..3ee5b2f3a72e4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -19,11 +18,13 @@ internal sealed class ConstructorInitializerSymbolReferenceFinder : AbstractRefe protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.Constructor; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -47,27 +48,27 @@ protected override Task> DetermineDocumentsToSearchAsyn } return false; - }, symbol.ContainingType.Name, cancellationToken); + }, symbol.ContainingType.Name, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var tokens = state.Cache.GetConstructorInitializerTokens(state.SyntaxFacts, state.Root, cancellationToken); if (state.SemanticModel.Language == LanguageNames.VisualBasic) - { - tokens = tokens.Concat(await FindMatchingIdentifierTokensAsync( - state, "New", cancellationToken).ConfigureAwait(false)).Distinct(); - } + tokens = tokens.Concat(FindMatchingIdentifierTokens(state, "New", cancellationToken)).Distinct(); var totalTokens = tokens.WhereAsArray( static (token, tuple) => TokensMatch(tuple.state, token, tuple.methodSymbol.ContainingType.Name, tuple.cancellationToken), (state, methodSymbol, cancellationToken)); - return await FindReferencesInTokensAsync(methodSymbol, state, totalTokens, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens(methodSymbol, state, totalTokens, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; // local functions static bool TokensMatch( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index ce48a413c061b..9e0d20f947dfd 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -32,80 +31,77 @@ protected override Task> DetermineGlobalAliasesAsync(IMet return GetAllMatchingGlobalAliasNamesAsync(project, containingType.Name, containingType.Arity, cancellationToken); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var containingType = symbol.ContainingType; var typeName = symbol.ContainingType.Name; - using var _ = ArrayBuilder.GetInstance(out var result); - await AddDocumentsAsync( - project, documents, typeName, result, cancellationToken).ConfigureAwait(false); + project, documents, typeName, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (globalAliases != null) { foreach (var globalAlias in globalAliases) { await AddDocumentsAsync( - project, documents, globalAlias, result, cancellationToken).ConfigureAwait(false); + project, documents, globalAlias, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } - result.AddRange(await FindDocumentsAsync( - project, documents, containingType.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false)); - - result.AddRange(await FindDocumentsWithGlobalSuppressMessageAttributeAsync( - project, documents, cancellationToken).ConfigureAwait(false)); + await FindDocumentsAsync( + project, documents, containingType.SpecialType.ToPredefinedType(), processResult, processResultData, cancellationToken).ConfigureAwait(false); - result.AddRange(symbol.MethodKind == MethodKind.Constructor - ? await FindDocumentsWithImplicitObjectCreationExpressionAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return result.ToImmutable(); + if (symbol.MethodKind == MethodKind.Constructor) + { + await FindDocumentsWithImplicitObjectCreationExpressionAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); + } } - private static Task> FindDocumentsWithImplicitObjectCreationExpressionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsImplicitObjectCreation, cancellationToken); + private static Task FindDocumentsWithImplicitObjectCreationExpressionAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsImplicitObjectCreation, processResult, processResultData, cancellationToken); - private static async Task AddDocumentsAsync( + private static async Task AddDocumentsAsync( Project project, IImmutableSet? documents, string typeName, - ArrayBuilder result, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, typeName).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, typeName).ConfigureAwait(false); - var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(typeName, project.Services.GetRequiredService(), out var simpleName) - ? await FindDocumentsAsync(project, documents, cancellationToken, simpleName).ConfigureAwait(false) - : []; - - result.AddRange(documentsWithName); - result.AddRange(documentsWithAttribute); + if (TryGetNameWithoutAttributeSuffix(typeName, project.Services.GetRequiredService(), out var simpleName)) + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, simpleName).ConfigureAwait(false); } private static bool IsPotentialReference(PredefinedType predefinedType, ISyntaxFactsService syntaxFacts, SyntaxToken token) => syntaxFacts.TryGetPredefinedType(token, out var actualType) && predefinedType == actualType; - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _1 = ArrayBuilder.GetInstance(out var result); - // First just look for this normal constructor references using the name of it's containing type. var name = methodSymbol.ContainingType.Name; - await AddReferencesInDocumentWorkerAsync( - methodSymbol, name, state, result, cancellationToken).ConfigureAwait(false); + AddReferencesInDocumentWorker( + methodSymbol, name, state, processResult, processResultData, cancellationToken); // Next, look for constructor references through a global alias to our containing type. foreach (var globalAlias in state.GlobalAliases) @@ -116,68 +112,64 @@ await AddReferencesInDocumentWorkerAsync( if (state.SyntaxFacts.StringComparer.Equals(name, globalAlias)) continue; - await AddReferencesInDocumentWorkerAsync( - methodSymbol, globalAlias, state, result, cancellationToken).ConfigureAwait(false); + AddReferencesInDocumentWorker( + methodSymbol, globalAlias, state, processResult, processResultData, cancellationToken); } - // Nest, our containing type might itself have local aliases to it in this particular file. - // If so, see what the local aliases are and then search for constructor references to that. - using var _2 = ArrayBuilder.GetInstance(out var typeReferences); - await NamedTypeSymbolReferenceFinder.AddReferencesToTypeOrGlobalAliasToItAsync( - methodSymbol.ContainingType, state, typeReferences, cancellationToken).ConfigureAwait(false); - - var aliasReferences = await FindLocalAliasReferencesAsync( - typeReferences, methodSymbol, state, cancellationToken).ConfigureAwait(false); - // Finally, look for constructor references to predefined types (like `new int()`), // implicit object references, and inside global suppression attributes. - result.AddRange(await FindPredefinedTypeReferencesAsync( - methodSymbol, state, cancellationToken).ConfigureAwait(false)); + FindPredefinedTypeReferences( + methodSymbol, state, processResult, processResultData, cancellationToken); - result.AddRange(await FindReferencesInImplicitObjectCreationExpressionAsync( - methodSymbol, state, cancellationToken).ConfigureAwait(false)); + FindReferencesInImplicitObjectCreationExpression( + methodSymbol, state, processResult, processResultData, cancellationToken); - result.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - methodSymbol, state, cancellationToken).ConfigureAwait(false)); + FindReferencesInDocumentInsideGlobalSuppressions( + methodSymbol, state, processResult, processResultData, cancellationToken); - return result.ToImmutable(); + return ValueTaskFactory.CompletedTask; } /// /// Finds references to in this , but only if it referenced /// though (which might be the actual name of the type, or a global alias to it). /// - private static async Task AddReferencesInDocumentWorkerAsync( + private static void AddReferencesInDocumentWorker( IMethodSymbol symbol, string name, FindReferencesDocumentState state, - ArrayBuilder result, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - result.AddRange(await FindOrdinaryReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); - result.AddRange(await FindAttributeReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); + FindOrdinaryReferences( + symbol, name, state, processResult, processResultData, cancellationToken); + FindAttributeReferences( + symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask> FindOrdinaryReferencesAsync( + private static void FindOrdinaryReferences( IMethodSymbol symbol, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync( - symbol, name, state, cancellationToken); + FindReferencesInDocumentUsingIdentifier( + symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask> FindPredefinedTypeReferencesAsync( + private static void FindPredefinedTypeReferences( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var predefinedType = symbol.ContainingType.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return new([]); + return; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -185,23 +177,26 @@ private static ValueTask> FindPredefinedTypeRefer static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask> FindAttributeReferencesAsync( + private static void FindAttributeReferences( IMethodSymbol symbol, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var simpleName) - ? FindReferencesInDocumentUsingIdentifierAsync(symbol, simpleName, state, cancellationToken) - : new([]); + if (TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var simpleName)) + FindReferencesInDocumentUsingIdentifier(symbol, simpleName, state, processResult, processResultData, cancellationToken); } - private Task> FindReferencesInImplicitObjectCreationExpressionAsync( + private void FindReferencesInImplicitObjectCreationExpression( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { // Only check `new (...)` calls that supply enough arguments to match all the required parameters for the constructor. @@ -214,13 +209,14 @@ private Task> FindReferencesInImplicitObjectCreat ? -1 : symbol.Parameters.Length; - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var syntaxFacts = state.SyntaxFacts; if (!syntaxFacts.IsImplicitObjectCreationExpression(node)) @@ -241,9 +237,10 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs index 610db4da1aeac..a6c1623d5ba95 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.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; @@ -15,23 +16,27 @@ internal sealed class DestructorSymbolReferenceFinder : AbstractReferenceFinder< protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.Destructor; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; } - protected override ValueTask> FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return new ValueTask>([]); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 756bffd4366c4..7b5870acdbde7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.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.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -33,25 +35,29 @@ protected sealed override ValueTask> DetermineCascadedSy return new(backingFields.Concat(associatedNamedTypes)); } - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IEventSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithName.Concat(documentsWithGlobalAttributes); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected sealed override ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IEventSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingSymbolNameAsync(symbol, state, cancellationToken); + FindReferencesInDocumentUsingSymbolName(symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs index bbf655f55c57d..dfed302b16041 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.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. +using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; @@ -23,11 +23,13 @@ protected override bool CanFind(IMethodSymbol symbol) private static INamedTypeSymbol? GetUnderlyingNamedType(ITypeSymbol symbol) => UnderlyingNamedTypeVisitor.Instance.Visit(symbol); - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -43,25 +45,25 @@ protected sealed override async Task> DetermineDocument var underlyingNamedType = GetUnderlyingNamedType(symbol.ReturnType); Contract.ThrowIfNull(underlyingNamedType); - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); - var documentsWithType = await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var result); + using var _ = PooledHashSet.GetInstance(out var result); + await FindDocumentsAsync(project, documents, StandardCallbacks.AddToHashSet, result, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), StandardCallbacks.AddToHashSet, result, cancellationToken).ConfigureAwait(false); // Ignore any documents that don't also have an explicit cast in them. - foreach (var document in documentsWithName.Concat(documentsWithType).Distinct()) + foreach (var document in result) { var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); if (index.ContainsConversion) - result.Add(document); + processResult(document, processResultData); } - - return result.ToImmutable(); } - protected sealed override ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -71,7 +73,8 @@ protected sealed override ValueTask> FindReferenc static (token, state) => IsPotentialReference(state.SyntaxFacts, token), state); - return FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs index 51256d807e4b0..a92e4f0ca1eba 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.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; @@ -15,25 +16,29 @@ internal sealed class ExplicitInterfaceMethodReferenceFinder : AbstractReference protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.ExplicitInterfaceImplementation; - protected sealed override Task> DetermineDocumentsToSearchAsync( + protected sealed override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // An explicit method can't be referenced anywhere. - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; } - protected sealed override ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // An explicit method can't be referenced anywhere. - return new([]); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index 43c3c03d7ccdc..4377ecd8bb5b5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.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; @@ -26,29 +27,32 @@ protected override ValueTask> DetermineCascadedSymbolsAs : new(ImmutableArray.Empty); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IFieldSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithName.Concat(documentsWithGlobalAttributes); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IFieldSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var nameReferences = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - return nameReferences.Concat(suppressionReferences); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs index 395fcb5402ca3..f5b717542dc20 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -49,9 +50,10 @@ ValueTask> DetermineCascadedSymbolsAsync( /// /// Implementations of this method must be thread-safe. /// - Task> DetermineDocumentsToSearchAsync( + Task DetermineDocumentsToSearchAsync( ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); /// @@ -61,9 +63,11 @@ Task> DetermineDocumentsToSearchAsync( /// /// Implementations of this method must be thread-safe. /// - ValueTask> FindReferencesInDocumentAsync( + ValueTask FindReferencesInDocumentAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs index 78d29392e7082..55150a8b42bf7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -36,11 +37,13 @@ protected override ValueTask> DetermineCascadedSymbolsAs return new([]); } - protected sealed override Task> DetermineDocumentsToSearchAsync( + protected sealed override Task DetermineDocumentsToSearchAsync( ITypeParameterSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -55,7 +58,7 @@ protected sealed override Task> DetermineDocumentsToSea // Also, we only look for files that have the name of the owning type. This helps filter // down the set considerably. Contract.ThrowIfNull(symbol.DeclaringMethod); - return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name, + return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name, GetMemberNameWithoutInterfaceName(symbol.DeclaringMethod.Name), symbol.DeclaringMethod.ContainingType.Name); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index 3dc76b005875f..0b2e5e08bb88c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -49,54 +50,49 @@ private static void Add(ArrayBuilder result, ImmutableArray()); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( INamedTypeSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var result); - - await AddDocumentsToSearchAsync(symbol.Name, project, documents, result, cancellationToken).ConfigureAwait(false); + await AddDocumentsToSearchAsync(symbol.Name, project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (globalAliases != null) { foreach (var alias in globalAliases) - await AddDocumentsToSearchAsync(alias, project, documents, result, cancellationToken).ConfigureAwait(false); + await AddDocumentsToSearchAsync(alias, project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - result.AddRange(await FindDocumentsAsync( - project, documents, symbol.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false)); - - result.AddRange(await FindDocumentsWithGlobalSuppressMessageAttributeAsync( - project, documents, cancellationToken).ConfigureAwait(false)); + await FindDocumentsAsync( + project, documents, symbol.SpecialType.ToPredefinedType(), processResult, processResultData, cancellationToken).ConfigureAwait(false); - return result.ToImmutable(); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } /// /// Looks for documents likely containing in them. That name will either be the actual /// name of the named type we're looking for, or it might be a global alias to it. /// - private static async Task AddDocumentsToSearchAsync( + private static async Task AddDocumentsToSearchAsync( string throughName, Project project, IImmutableSet? documents, - ArrayBuilder result, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var syntaxFacts = project.Services.GetRequiredService(); - var documentsWithName = await FindDocumentsAsync( - project, documents, cancellationToken, throughName).ConfigureAwait(false); + await FindDocumentsAsync( + project, documents, processResult, processResultData, cancellationToken, throughName).ConfigureAwait(false); - var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(throughName, syntaxFacts, out var simpleName) - ? await FindDocumentsAsync(project, documents, cancellationToken, simpleName).ConfigureAwait(false) - : []; - - result.AddRange(documentsWithName); - result.AddRange(documentsWithAttribute); + if (TryGetNameWithoutAttributeSuffix(throughName, syntaxFacts, out var simpleName)) + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, simpleName).ConfigureAwait(false); } private static bool IsPotentialReference( @@ -109,9 +105,11 @@ private static bool IsPotentialReference( predefinedType == actualType; } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( INamedTypeSymbol namedType, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -119,32 +117,37 @@ protected override async ValueTask> FindReference // First find all references to this type, either with it's actual name, or through potential // global alises to it. - await AddReferencesToTypeOrGlobalAliasToItAsync( - namedType, state, initialReferences, cancellationToken).ConfigureAwait(false); + AddReferencesToTypeOrGlobalAliasToIt( + namedType, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken); + + // The items in initialReferences need to be both reported and used later to calculate additional results. + foreach (var location in initialReferences) + processResult(location, processResultData); // This named type may end up being locally aliased as well. If so, now find all the references // to the local alias. - initialReferences.AddRange(await FindLocalAliasReferencesAsync( - initialReferences, state, cancellationToken).ConfigureAwait(false)); + FindLocalAliasReferences( + initialReferences, state, processResult, processResultData, cancellationToken); - initialReferences.AddRange(await FindPredefinedTypeReferencesAsync( - namedType, state, cancellationToken).ConfigureAwait(false)); + FindPredefinedTypeReferences( + namedType, state, processResult, processResultData, cancellationToken); - initialReferences.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - namedType, state, cancellationToken).ConfigureAwait(false)); + FindReferencesInDocumentInsideGlobalSuppressions( + namedType, state, processResult, processResultData, cancellationToken); - return initialReferences.ToImmutable(); + return ValueTaskFactory.CompletedTask; } - internal static async ValueTask AddReferencesToTypeOrGlobalAliasToItAsync( + internal static void AddReferencesToTypeOrGlobalAliasToIt( INamedTypeSymbol namedType, FindReferencesDocumentState state, - ArrayBuilder nonAliasReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - await AddNonAliasReferencesAsync( - namedType, namedType.Name, state, nonAliasReferences, cancellationToken).ConfigureAwait(false); + AddNonAliasReferences( + namedType, namedType.Name, state, processResult, processResultData, cancellationToken); foreach (var globalAlias in state.GlobalAliases) { @@ -154,8 +157,8 @@ await AddNonAliasReferencesAsync( if (state.SyntaxFacts.StringComparer.Equals(namedType.Name, globalAlias)) continue; - await AddNonAliasReferencesAsync( - namedType, globalAlias, state, nonAliasReferences, cancellationToken).ConfigureAwait(false); + AddNonAliasReferences( + namedType, globalAlias, state, processResult, processResultData, cancellationToken); } } @@ -164,24 +167,27 @@ await AddNonAliasReferencesAsync( /// only if it referenced though (which might be the actual name /// of the type, or a global alias to it). /// - private static async ValueTask AddNonAliasReferencesAsync( + private static void AddNonAliasReferences( INamedTypeSymbol symbol, string name, FindReferencesDocumentState state, - ArrayBuilder nonAliasesReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - nonAliasesReferences.AddRange(await FindOrdinaryReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); + FindOrdinaryReferences( + symbol, name, state, processResult, processResultData, cancellationToken); - nonAliasesReferences.AddRange(await FindAttributeReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); + FindAttributeReferences( + symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask> FindOrdinaryReferencesAsync( + private static void FindOrdinaryReferences( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { // Get the parent node that best matches what this token represents. For example, if we have `new a.b()` @@ -189,18 +195,20 @@ private static ValueTask> FindOrdinaryReferencesA // to the constructor not the type. That's a good thing as we don't want these object-creations to // associate with the type, but rather with the constructor itself. - return FindReferencesInDocumentUsingIdentifierAsync( - namedType, name, state, cancellationToken); + FindReferencesInDocumentUsingIdentifier( + namedType, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask> FindPredefinedTypeReferencesAsync( + private static void FindPredefinedTypeReferences( INamedTypeSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var predefinedType = symbol.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return new([]); + return; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -208,17 +216,18 @@ private static ValueTask> FindPredefinedTypeRefer static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask> FindAttributeReferencesAsync( + private static void FindAttributeReferences( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix) - ? FindReferencesInDocumentUsingIdentifierAsync(namedType, nameWithoutSuffix, state, cancellationToken) - : new([]); + if (TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix)) + FindReferencesInDocumentUsingIdentifier(namedType, nameWithoutSuffix, state, processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index 1759897904691..3ca848269ce6a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -9,6 +10,7 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -22,53 +24,53 @@ protected override Task> DetermineGlobalAliasesAsync(INam return GetAllMatchingGlobalAliasNamesAsync(project, symbol.Name, arity: 0, cancellationToken); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( INamespaceSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var result); - - result.AddRange(!symbol.IsGlobalNamespace - ? await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false) - : await FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsGlobalKeyword, cancellationToken).ConfigureAwait(false)); + if (!symbol.IsGlobalNamespace) + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); + else + await FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsGlobalKeyword, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (globalAliases != null) { foreach (var globalAlias in globalAliases) { - result.AddRange(await FindDocumentsAsync( - project, documents, cancellationToken, globalAlias).ConfigureAwait(false)); + await FindDocumentsAsync( + project, documents, processResult, processResultData, cancellationToken, globalAlias).ConfigureAwait(false); } } - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - result.AddRange(documentsWithGlobalAttributes); - - return result.ToImmutable(); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( INamespaceSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var initialReferences); - if (symbol.IsGlobalNamespace) { - await AddGlobalNamespaceReferencesAsync( - symbol, state, initialReferences, cancellationToken).ConfigureAwait(false); + AddGlobalNamespaceReferences( + symbol, state, processResult, processResultData, cancellationToken); } else { + using var _ = ArrayBuilder.GetInstance(out var initialReferences); + var namespaceName = symbol.Name; - await AddNamedReferencesAsync( - symbol, namespaceName, state, initialReferences, cancellationToken).ConfigureAwait(false); + AddNamedReferences( + symbol, namespaceName, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken); foreach (var globalAlias in state.GlobalAliases) { @@ -78,42 +80,47 @@ await AddNamedReferencesAsync( if (state.SyntaxFacts.StringComparer.Equals(namespaceName, globalAlias)) continue; - await AddNamedReferencesAsync( - symbol, globalAlias, state, initialReferences, cancellationToken).ConfigureAwait(false); + AddNamedReferences( + symbol, globalAlias, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken); } - initialReferences.AddRange(await FindLocalAliasReferencesAsync( - initialReferences, symbol, state, cancellationToken).ConfigureAwait(false)); + // The items in initialReferences need to be both reported and used later to calculate additional results. + foreach (var location in initialReferences) + processResult(location, processResultData); + + FindLocalAliasReferences( + initialReferences, symbol, state, processResult, processResultData, cancellationToken); - initialReferences.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false)); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); } - return initialReferences.ToImmutable(); + return ValueTaskFactory.CompletedTask; } /// /// Finds references to in this , but only if it referenced /// though (which might be the actual name of the type, or a global alias to it). /// - private static async ValueTask AddNamedReferencesAsync( + private static void AddNamedReferences( INamespaceSymbol symbol, string name, FindReferencesDocumentState state, - ArrayBuilder initialReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - var tokens = await FindMatchingIdentifierTokensAsync( - state, name, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, name, cancellationToken); - initialReferences.AddRange(await FindReferencesInTokensAsync( - symbol, state, tokens, cancellationToken).ConfigureAwait(false)); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static async Task AddGlobalNamespaceReferencesAsync( + private static void AddGlobalNamespaceReferences( INamespaceSymbol symbol, FindReferencesDocumentState state, - ArrayBuilder initialReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var tokens = state.Root @@ -122,7 +129,7 @@ private static async Task AddGlobalNamespaceReferencesAsync( static (token, state) => state.SyntaxFacts.IsGlobalNamespaceKeyword(token), state); - initialReferences.AddRange(await FindReferencesInTokensAsync( - symbol, state, tokens, cancellationToken).ConfigureAwait(false)); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index a4135e8d5f065..a6d5cb8b80c0c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.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; @@ -17,36 +18,41 @@ internal sealed class OperatorSymbolReferenceFinder : AbstractMethodOrPropertyOr protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind is MethodKind.UserDefinedOperator or MethodKind.BuiltinOperator; - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var op = symbol.GetPredefinedOperator(); - var documentsWithOp = await FindDocumentsAsync(project, documents, op, cancellationToken).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithOp.Concat(documentsWithGlobalAttributes); + await FindDocumentsAsync(project, documents, op, processResult, processResultData, cancellationToken).ConfigureAwait(false); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static Task> FindDocumentsAsync( + private static Task FindDocumentsAsync( Project project, IImmutableSet? documents, PredefinedOperator op, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (op == PredefinedOperator.None) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; return FindDocumentsWithPredicateAsync( - project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, cancellationToken); + project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -57,12 +63,11 @@ protected sealed override async ValueTask> FindRe static (token, tuple) => IsPotentialReference(tuple.state.SyntaxFacts, tuple.op, token), (state, op)); - var opReferences = await FindReferencesInTokensAsync( - symbol, state, tokens, cancellationToken).ConfigureAwait(false); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - - return opReferences.Concat(suppressionReferences); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index 91ab4963aa113..bb1dbb697400a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -2,10 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -42,11 +44,13 @@ private static ImmutableArray GetOtherPartsOfPartial(IMethodSymbol symb return []; } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IMethodSymbol methodSymbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -65,38 +69,32 @@ protected override async Task> DetermineDocumentsToSear // searches for these, then we should find usages of 'lock(goo)' or 'synclock(goo)' // since they implicitly call those methods. - var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, methodSymbol.Name).ConfigureAwait(false); - var forEachDocuments = IsForEachMethod(methodSymbol) - ? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, methodSymbol.Name).ConfigureAwait(false); - var deconstructDocuments = IsDeconstructMethod(methodSymbol) - ? await FindDocumentsWithDeconstructionAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachMethod(methodSymbol)) + await FindDocumentsWithForEachStatementsAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var awaitExpressionDocuments = IsGetAwaiterMethod(methodSymbol) - ? await FindDocumentsWithAwaitExpressionAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (IsDeconstructMethod(methodSymbol)) + await FindDocumentsWithDeconstructionAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync( - project, documents, cancellationToken).ConfigureAwait(false); + if (IsGetAwaiterMethod(methodSymbol)) + await FindDocumentsWithAwaitExpressionAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var documentsWithCollectionInitializers = IsAddMethod(methodSymbol) - ? await FindDocumentsWithCollectionInitializersAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + await FindDocumentsWithGlobalSuppressMessageAttributeAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return ordinaryDocuments.Concat( - forEachDocuments, deconstructDocuments, awaitExpressionDocuments, documentsWithGlobalAttributes, documentsWithCollectionInitializers); + if (IsAddMethod(methodSymbol)) + await FindDocumentsWithCollectionInitializersAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static Task> FindDocumentsWithDeconstructionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsDeconstruction, cancellationToken); + private static Task FindDocumentsWithDeconstructionAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsDeconstruction, processResult, processResultData, cancellationToken); - private static Task> FindDocumentsWithAwaitExpressionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsAwait, cancellationToken); + private static Task FindDocumentsWithAwaitExpressionAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsAwait, processResult, processResultData, cancellationToken); - private static Task> FindDocumentsWithCollectionInitializersAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsCollectionInitializer, cancellationToken); + private static Task FindDocumentsWithCollectionInitializersAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsCollectionInitializer, processResult, processResultData, cancellationToken); private static bool IsForEachMethod(IMethodSymbol methodSymbol) => methodSymbol.Name is WellKnownMemberNames.GetEnumeratorMethodName or @@ -111,34 +109,32 @@ private static bool IsGetAwaiterMethod(IMethodSymbol methodSymbol) private static bool IsAddMethod(IMethodSymbol methodSymbol) => methodSymbol.Name == WellKnownMemberNames.CollectionInitializerAddMethodName; - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var nameMatches = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); - var forEachMatches = IsForEachMethod(symbol) - ? await FindReferencesInForEachStatementsAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachMethod(symbol)) + FindReferencesInForEachStatements(symbol, state, processResult, processResultData, cancellationToken); - var deconstructMatches = IsDeconstructMethod(symbol) - ? await FindReferencesInDeconstructionAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (IsDeconstructMethod(symbol)) + FindReferencesInDeconstruction(symbol, state, processResult, processResultData, cancellationToken); - var getAwaiterMatches = IsGetAwaiterMethod(symbol) - ? await FindReferencesInAwaitExpressionAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (IsGetAwaiterMethod(symbol)) + FindReferencesInAwaitExpression(symbol, state, processResult, processResultData, cancellationToken); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); - var addMatches = IsAddMethod(symbol) - ? await FindReferencesInCollectionInitializerAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (IsAddMethod(symbol)) + FindReferencesInCollectionInitializer(symbol, state, processResult, processResultData, cancellationToken); - return nameMatches.Concat(forEachMatches, deconstructMatches, getAwaiterMatches, suppressionReferences, addMatches); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index ef3cde6005202..adef39983674b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -20,11 +21,13 @@ internal sealed class ParameterSymbolReferenceFinder : AbstractReferenceFinder true; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IParameterSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -33,16 +36,19 @@ protected override Task> DetermineDocumentsToSearchAsyn // elsewhere as "paramName:" or "paramName:=". We can narrow the search by // filtering down to matches of that form. For now we just return any document // that references something with this name. - return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name); + return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name); } - protected override ValueTask> FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IParameterSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync(symbol, symbol.Name, state, cancellationToken); + FindReferencesInDocumentUsingIdentifier(symbol, symbol.Name, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } protected override async ValueTask> DetermineCascadedSymbolsAsync( @@ -63,7 +69,7 @@ protected override async ValueTask> DetermineCascadedSym CascadeBetweenPrimaryConstructorParameterAndProperties(parameter, symbols, cancellationToken); CascadeBetweenAnonymousDelegateParameters(parameter, symbols); - return symbols.ToImmutable(); + return symbols.ToImmutableAndClear(); } private static void CascadeBetweenAnonymousDelegateParameters(IParameterSymbol parameter, ArrayBuilder symbols) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index 383c82ad44096..cf0a657769c89 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -30,64 +29,66 @@ protected override ValueTask> DetermineCascadedSymbolsAs : new(ImmutableArray.Create(symbol.AssociatedSymbol)); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // First, find any documents with the full name of the accessor (i.e. get_Goo). // This will find explicit calls to the method (which can happen when C# references // a VB parameterized property). - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); - var propertyDocuments = ImmutableArray.Empty; if (symbol.AssociatedSymbol is IPropertySymbol property && options.AssociatePropertyReferencesWithSpecificAccessor) { // we want to associate normal property references with the specific accessor being // referenced. So we also need to include documents with our property's name. Just // defer to the Property finder to find these docs and combine them with the result. - propertyDocuments = await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( + await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( property, globalAliases, project, documents, + processResult, processResultData, options with { AssociatePropertyReferencesWithSpecificAccessor = false }, cancellationToken).ConfigureAwait(false); } - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithName.Concat(propertyDocuments, documentsWithGlobalAttributes); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var references = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); if (symbol.AssociatedSymbol is not IPropertySymbol property || !options.AssociatePropertyReferencesWithSpecificAccessor) { - return references; + return; } - var propertyReferences = await ReferenceFinders.Property.FindReferencesInDocumentAsync( - property, state, - options with { AssociatePropertyReferencesWithSpecificAccessor = false }, - cancellationToken).ConfigureAwait(false); - - var accessorReferences = propertyReferences.WhereAsArray( - loc => + await ReferenceFinders.Property.FindReferencesInDocumentAsync( + property, + state, + static (loc, data) => { var accessors = GetReferencedAccessorSymbols( - state, property, loc.Node, cancellationToken); - return accessors.Contains(symbol); - }); - - return references.Concat(accessorReferences); + data.state, data.property, loc.Node, data.cancellationToken); + if (accessors.Contains(data.symbol)) + data.processResult(loc, data.processResultData); + }, + (property, symbol, state, processResult, processResultData, cancellationToken), + options with { AssociatePropertyReferencesWithSpecificAccessor = false }, + cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 05bdb9bf8bfa1..d5fed2c94c5aa 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -91,87 +92,93 @@ private static void CascadeToPrimaryConstructorParameters(IPropertySymbol proper } } - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IPropertySymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); - var forEachDocuments = IsForEachProperty(symbol) - ? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachProperty(symbol)) + await FindDocumentsWithForEachStatementsAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var elementAccessDocument = symbol.IsIndexer - ? await FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (symbol.IsIndexer) + await FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var indexerMemberCrefDocument = symbol.IsIndexer - ? await FindDocumentWithIndexerMemberCrefAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (symbol.IsIndexer) + await FindDocumentWithIndexerMemberCrefAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return ordinaryDocuments.Concat(forEachDocuments, elementAccessDocument, indexerMemberCrefDocument, documentsWithGlobalAttributes); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } private static bool IsForEachProperty(IPropertySymbol symbol) => symbol.Name == WellKnownMemberNames.CurrentPropertyName; - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IPropertySymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var nameReferences = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - - if (options.AssociatePropertyReferencesWithSpecificAccessor) - { - // We want to associate property references to a specific accessor (if an accessor - // is being referenced). Check if this reference would match an accessor. If so, do - // not add it. It will be added by PropertyAccessorSymbolReferenceFinder. - nameReferences = nameReferences.WhereAsArray(loc => + FindReferencesInDocumentUsingSymbolName( + symbol, + state, + static (loc, data) => { - var accessors = GetReferencedAccessorSymbols( - state, symbol, loc.Node, cancellationToken); - return accessors.IsEmpty; - }); - } + var useResult = true; + if (data.options.AssociatePropertyReferencesWithSpecificAccessor) + { + // We want to associate property references to a specific accessor (if an accessor + // is being referenced). Check if this reference would match an accessor. If so, do + // not add it. It will be added by PropertyAccessorSymbolReferenceFinder. + var accessors = GetReferencedAccessorSymbols( + data.state, data.symbol, loc.Node, data.cancellationToken); + useResult = accessors.IsEmpty; + } - var forEachReferences = IsForEachProperty(symbol) - ? await FindReferencesInForEachStatementsAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (useResult) + data.processResult(loc, data.processResultData); + }, + processResultData: (self: this, symbol, state, processResult, processResultData, options, cancellationToken), + cancellationToken); - var indexerReferences = symbol.IsIndexer - ? await FindIndexerReferencesAsync(symbol, state, options, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachProperty(symbol)) + FindReferencesInForEachStatements(symbol, state, processResult, processResultData, cancellationToken); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - return nameReferences.Concat(forEachReferences, indexerReferences, suppressionReferences); + if (symbol.IsIndexer) + FindIndexerReferences(symbol, state, processResult, processResultData, options, cancellationToken); + + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } - private static Task> FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( - Project project, IImmutableSet? documents, CancellationToken cancellationToken) + private static Task FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( + Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( - project, documents, static index => index.ContainsExplicitOrImplicitElementAccessExpression, cancellationToken); + project, documents, static index => index.ContainsExplicitOrImplicitElementAccessExpression, processResult, processResultData, cancellationToken); } - private static Task> FindDocumentWithIndexerMemberCrefAsync( - Project project, IImmutableSet? documents, CancellationToken cancellationToken) + private static Task FindDocumentWithIndexerMemberCrefAsync( + Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( - project, documents, static index => index.ContainsIndexerMemberCref, cancellationToken); + project, documents, static index => index.ContainsIndexerMemberCref, processResult, processResultData, cancellationToken); } - private static async Task> FindIndexerReferencesAsync( + private static void FindIndexerReferences( IPropertySymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -179,7 +186,7 @@ private static async Task> FindIndexerReferencesA { // Looking for individual get/set references. Don't find anything here. // these results will be provided by the PropertyAccessorSymbolReferenceFinder - return []; + return; } var syntaxFacts = state.SyntaxFacts; @@ -190,31 +197,28 @@ private static async Task> FindIndexerReferencesA syntaxFacts.IsImplicitElementAccess(node) || syntaxFacts.IsConditionalAccessExpression(node) || syntaxFacts.IsIndexerMemberCref(node)); - using var _ = ArrayBuilder.GetInstance(out var locations); foreach (var node in indexerReferenceExpressions) { cancellationToken.ThrowIfCancellationRequested(); - var (matched, candidateReason, indexerReference) = await ComputeIndexerInformationAsync( - symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, candidateReason, indexerReference) = ComputeIndexerInformation(symbol, state, node, cancellationToken); if (!matched) continue; var location = state.SyntaxTree.GetLocation(new TextSpan(indexerReference.SpanStart, 0)); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: false, symbolUsageInfo, GetAdditionalFindUsagesProperties(node, state), - candidateReason))); + candidateReason)); + processResult(result, processResultData); } - - return locations.ToImmutable(); } - private static ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeIndexerInformation( IPropertySymbol symbol, FindReferencesDocumentState state, SyntaxNode node, @@ -225,33 +229,33 @@ private static async Task> FindIndexerReferencesA if (syntaxFacts.IsElementAccessExpression(node)) { // The indexerReference for an element access expression will not be null - return ComputeElementAccessInformationAsync(symbol, node, state, cancellationToken)!; + return ComputeElementAccessInformation(symbol, node, state, cancellationToken)!; } else if (syntaxFacts.IsImplicitElementAccess(node)) { - return ComputeImplicitElementAccessInformationAsync(symbol, node, state, cancellationToken)!; + return ComputeImplicitElementAccessInformation(symbol, node, state, cancellationToken)!; } else if (syntaxFacts.IsConditionalAccessExpression(node)) { - return ComputeConditionalAccessInformationAsync(symbol, node, state, cancellationToken); + return ComputeConditionalAccessInformation(symbol, node, state, cancellationToken); } else { Debug.Assert(syntaxFacts.IsIndexerMemberCref(node)); - return ComputeIndexerMemberCRefInformationAsync(symbol, state, node, cancellationToken); + return ComputeIndexerMemberCRefInformation(symbol, state, node, cancellationToken); } } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerMemberCRefInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeIndexerMemberCRefInformation( IPropertySymbol symbol, FindReferencesDocumentState state, SyntaxNode node, CancellationToken cancellationToken) { - var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, node, cancellationToken); // For an IndexerMemberCRef the node itself is the indexer we are looking for. return (matched, reason, node); } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeConditionalAccessInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeConditionalAccessInformation( IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) { // For a ConditionalAccessExpression the whenNotNull component is the indexer reference we are looking for @@ -266,16 +270,16 @@ private static async Task> FindIndexerReferencesA return default; } - var (matched, reason) = await SymbolsMatchAsync(symbol, state, indexerReference, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, indexerReference, cancellationToken); return (matched, reason, indexerReference); } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode? indexerReference)> ComputeElementAccessInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode? indexerReference) ComputeElementAccessInformation( IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) { // For an ElementAccessExpression the indexer we are looking for is the argumentList component. state.SyntaxFacts.GetPartsOfElementAccessExpression(node, out var expression, out var indexerReference); - if (expression != null && (await SymbolsMatchAsync(symbol, state, expression, cancellationToken).ConfigureAwait(false)).matched) + if (expression != null && SymbolsMatch(symbol, state, expression, cancellationToken).matched) { // Element access with explicit member name (allowed in VB). We will have // already added a reference location for the member name identifier, so skip @@ -283,15 +287,15 @@ private static async Task> FindIndexerReferencesA return default; } - var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, node, cancellationToken); return (matched, reason, indexerReference); } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeImplicitElementAccessInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeImplicitElementAccessInformation( IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) { var argumentList = state.SyntaxFacts.GetArgumentListOfImplicitElementAccess(node); - var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, node, cancellationToken); return (matched, reason, argumentList); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs index 32587f95c601e..6b1ffc51734be 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -14,11 +15,13 @@ internal sealed class TypeParameterSymbolReferenceFinder : AbstractTypeParameter protected override bool CanFind(ITypeParameterSymbol symbol) => symbol.TypeParameterKind != TypeParameterKind.Method; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( ITypeParameterSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -29,6 +32,6 @@ protected override Task> DetermineDocumentsToSearchAsyn // parameter has a different name in different parts that we won't find it. However, // this only happens in error situations. It is not legal in C# to use a different // name for a type parameter in different parts. - return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name, symbol.ContainingType.Name); + return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name, symbol.ContainingType.Name); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingSymbolHashSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingSymbolHashSet.cs index 906b70075c4fb..7f31eb6cc5306 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingSymbolHashSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingSymbolHashSet.cs @@ -3,12 +3,24 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.FindSymbols; internal sealed class MetadataUnifyingSymbolHashSet : HashSet { + private static readonly ObjectPool s_metadataUnifyingSymbolHashSetPool = new(() => []); + public MetadataUnifyingSymbolHashSet() : base(MetadataUnifyingEquivalenceComparer.Instance) { } + + public static MetadataUnifyingSymbolHashSet AllocateFromPool() + => s_metadataUnifyingSymbolHashSetPool.Allocate(); + + public static void ClearAndFree(MetadataUnifyingSymbolHashSet set) + { + set.Clear(); + s_metadataUnifyingSymbolHashSetPool.Free(set); + } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs index 511fd7fb42fd7..b10cd55dfc0e9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.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.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -26,9 +27,7 @@ private NoOpStreamingFindReferencesProgress() 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; + public ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) => default; private class NoOpProgressTracker : IStreamingProgressTracker { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs new file mode 100644 index 0000000000000..5696c32d2e8b9 --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.FindSymbols; + +internal static class StandardCallbacks +{ + public static readonly Action> AddToHashSet = + static (data, set) => set.Add(data); + + public static readonly Action> AddToArrayBuilder = + static (data, builder) => builder.Add(data); +} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs index 0a13aa5929644..32a4ce2080823 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs @@ -3,6 +3,7 @@ // 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.ErrorReporting; @@ -37,18 +38,6 @@ public ValueTask OnCompletedAsync(CancellationToken cancellationToken) return default; } - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) - { - _progress.OnFindInDocumentCompleted(document); - return default; - } - - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) - { - _progress.OnFindInDocumentStarted(document); - return default; - } - public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { try @@ -64,9 +53,11 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can } } - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + public ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - _progress.OnReferenceFound(symbol, location); + foreach (var (_, symbol, location) in references) + _progress.OnReferenceFound(symbol, location); + return default; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index 1f3b3736c5090..2c95215a67b28 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -19,10 +19,8 @@ internal interface ICallback ValueTask ReferenceItemsCompletedAsync(RemoteServiceCallbackId callbackId, int count, 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 OnReferencesFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray<(SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken); ValueTask AddLiteralItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); ValueTask LiteralItemsCompletedAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index fa7a34043bc62..5a84f3f20655e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -72,11 +72,8 @@ internal interface IStreamingFindReferencesProgress ValueTask OnStartedAsync(CancellationToken cancellationToken); ValueTask OnCompletedAsync(CancellationToken cancellationToken); - ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken); - ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken); - ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken); - ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken); + ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken); } internal interface IStreamingFindLiteralReferencesProgress diff --git a/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs b/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs index 3183048b5c89f..dd58ce4082086 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs @@ -64,14 +64,13 @@ internal partial class AbstractSyntaxIndex try { var storage = await storageService.GetStorageAsync(documentKey.Project.Solution, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); // attempt to load from persisted state using var stream = await storage.ReadStreamAsync(documentKey, s_persistenceName, checksum, cancellationToken).ConfigureAwait(false); if (stream != null) { using var gzipStream = new GZipStream(stream, CompressionMode.Decompress, leaveOpen: true); - using var reader = ObjectReader.TryGetReader(gzipStream, cancellationToken: cancellationToken); + using var reader = ObjectReader.TryGetReader(gzipStream); if (reader != null) return read(stringTable, reader, checksum); } @@ -156,12 +155,11 @@ private async Task SaveAsync( try { var storage = await persistentStorageService.GetStorageAsync(solutionKey, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); using (var stream = SerializableBytes.CreateWritableStream()) { using (var gzipStream = new GZipStream(stream, CompressionLevel.Optimal, leaveOpen: true)) - using (var writer = new ObjectWriter(gzipStream, leaveOpen: true, cancellationToken)) + using (var writer = new ObjectWriter(gzipStream, leaveOpen: true)) { WriteTo(writer); gzipStream.Flush(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index dfc42e1bf5983..912ae358bea4b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -40,20 +40,17 @@ public ImmutableArray GetReferencedSymbols() { lock (_gate) { - using var _ = ArrayBuilder.GetInstance(out var result); + var result = new FixedSizeArrayBuilder(_symbolToLocations.Count); foreach (var (symbol, locations) in _symbolToLocations) - result.Add(new ReferencedSymbol(symbol, locations.ToImmutableArray())); + result.Add(new ReferencedSymbol(symbol, [.. locations])); - return result.ToImmutable(); + return result.MoveToImmutable(); } } public ValueTask OnStartedAsync(CancellationToken cancellationToken) => underlyingProgress.OnStartedAsync(cancellationToken); public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => underlyingProgress.OnCompletedAsync(cancellationToken); - 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(SymbolGroup group, CancellationToken cancellationToken) { try @@ -72,13 +69,15 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can } } - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation location, CancellationToken cancellationToken) + public ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { lock (_gate) { - _symbolToLocations[definition].Add(location); + foreach (var (_, definition, location) in references) + _symbolToLocations[definition].Add(location); } - return underlyingProgress.OnReferenceFoundAsync(group, definition, location, cancellationToken); + return underlyingProgress.OnReferencesFoundAsync(references, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs index 1c4eaad54f7a0..84d6270bf7145 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; @@ -43,14 +44,8 @@ public ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId, Cancellati public ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup, CancellationToken cancellationToken) => GetFindReferencesCallback(callbackId).OnDefinitionFoundAsync(symbolGroup, cancellationToken); - public ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken) - => GetFindReferencesCallback(callbackId).OnFindInDocumentCompletedAsync(documentId, cancellationToken); - - public ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken) - => GetFindReferencesCallback(callbackId).OnFindInDocumentStartedAsync(documentId, cancellationToken); - - public ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference, CancellationToken cancellationToken) - => GetFindReferencesCallback(callbackId).OnReferenceFoundAsync(symbolGroup, definition, reference, cancellationToken); + public ValueTask OnReferencesFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray<(SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).OnReferencesFoundAsync(references, cancellationToken); public ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) => GetFindReferencesCallback(callbackId).OnStartedAsync(cancellationToken); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 89be7cdadb127..2830187083028 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -8,8 +8,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols; @@ -40,18 +38,6 @@ public ValueTask OnStartedAsync(CancellationToken cancellationToken) public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => progress.OnCompletedAsync(cancellationToken); - public async ValueTask OnFindInDocumentStartedAsync(DocumentId documentId, CancellationToken cancellationToken) - { - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - await progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); - } - - public async ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId, CancellationToken cancellationToken) - { - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - await progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); - } - public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated, CancellationToken cancellationToken) { Contract.ThrowIfTrue(dehydrated.Symbols.Count == 0); @@ -67,7 +53,7 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated map[symbolAndProjectId] = symbol; } - var symbolGroup = new SymbolGroup(map.Values.ToImmutableArray()); + var symbolGroup = new SymbolGroup([.. map.Values]); lock (_gate) { _groupMap[dehydrated] = symbolGroup; @@ -78,34 +64,38 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated await progress.OnDefinitionFoundAsync(symbolGroup, cancellationToken).ConfigureAwait(false); } - public async ValueTask OnReferenceFoundAsync( - SerializableSymbolGroup serializableSymbolGroup, - SerializableSymbolAndProjectId serializableSymbol, - SerializableReferenceLocation reference, + public async ValueTask OnReferencesFoundAsync( + ImmutableArray<(SerializableSymbolGroup serializableSymbolGroup, SerializableSymbolAndProjectId serializableSymbol, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken) { - SymbolGroup? symbolGroup; - ISymbol? symbol; - lock (_gate) + using var _ = ArrayBuilder<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)>.GetInstance(references.Length, out var rehydrated); + foreach (var (serializableSymbolGroup, serializableSymbol, reference) in references) { - // The definition may not be in the map if we failed to map it over using TryRehydrateAsync in OnDefinitionFoundAsync. - // Just ignore this reference. Note: while this is a degraded experience: - // - // 1. TryRehydrateAsync logs an NFE so we can track down while we're failing to roundtrip the - // 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 (!_groupMap.TryGetValue(serializableSymbolGroup, out symbolGroup) || - !_definitionMap.TryGetValue(serializableSymbol, out symbol)) + SymbolGroup? symbolGroup; + ISymbol? symbol; + lock (_gate) { - return; + // The definition may not be in the map if we failed to map it over using TryRehydrateAsync in OnDefinitionFoundAsync. + // Just ignore this reference. Note: while this is a degraded experience: + // + // 1. TryRehydrateAsync logs an NFE so we can track down while we're failing to roundtrip the + // 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 (!_groupMap.TryGetValue(serializableSymbolGroup, out symbolGroup) || + !_definitionMap.TryGetValue(serializableSymbol, out symbol)) + { + continue; + } } - } - var referenceLocation = await reference.RehydrateAsync( - solution, cancellationToken).ConfigureAwait(false); + var referenceLocation = await reference.RehydrateAsync( + solution, cancellationToken).ConfigureAwait(false); + rehydrated.Add((symbolGroup, symbol, referenceLocation)); + } - await progress.OnReferenceFoundAsync(symbolGroup, symbol, referenceLocation, cancellationToken).ConfigureAwait(false); + if (rehydrated.Count > 0) + await progress.OnReferencesFoundAsync(rehydrated.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs index f69e50e9a3529..89ac7f1794849 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs @@ -308,6 +308,6 @@ internal static async Task> FindLinkedSymbolsAsync( } } - return linkedSymbols.ToImmutableArray(); + return [.. linkedSymbols]; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs index 862b5670e8d78..6cfe823cd8584 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs @@ -63,7 +63,7 @@ internal static async Task> FindSourceDeclarationsWithCu result.AddRange(symbols); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs index 76d59fc83456d..c0d596fa2f7ed 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs @@ -30,11 +30,10 @@ Accessibility.Protected or return true; } - internal static async Task OriginalSymbolsMatchAsync( + internal static bool OriginalSymbolsMatch( Solution solution, ISymbol? searchSymbol, - ISymbol? symbolToMatch, - CancellationToken cancellationToken) + ISymbol? symbolToMatch) { if (ReferenceEquals(searchSymbol, symbolToMatch)) return true; @@ -48,7 +47,7 @@ internal static async Task OriginalSymbolsMatchAsync( if (searchSymbol.Equals(symbolToMatch)) return true; - if (await OriginalSymbolsMatchCoreAsync(solution, searchSymbol, symbolToMatch, cancellationToken).ConfigureAwait(false)) + if (OriginalSymbolsMatchCore(solution, searchSymbol, symbolToMatch)) return true; if (searchSymbol.Kind == SymbolKind.Namespace && symbolToMatch.Kind == SymbolKind.Namespace) @@ -60,8 +59,8 @@ internal static async Task OriginalSymbolsMatchAsync( var namespace2Count = namespace2.ConstituentNamespaces.Length; if (namespace1Count != namespace2Count) { - if ((namespace1Count > 1 && await namespace1.ConstituentNamespaces.AnyAsync(static (n, arg) => NamespaceSymbolsMatchAsync(arg.solution, n, arg.namespace2, arg.cancellationToken), (solution, namespace2, cancellationToken)).ConfigureAwait(false)) || - (namespace2Count > 1 && await namespace2.ConstituentNamespaces.AnyAsync(static (n2, arg) => NamespaceSymbolsMatchAsync(arg.solution, arg.namespace1, n2, arg.cancellationToken), (solution, namespace1, cancellationToken)).ConfigureAwait(false))) + if ((namespace1Count > 1 && namespace1.ConstituentNamespaces.Any(static (n, arg) => OriginalSymbolsMatch(arg.solution, n, arg.namespace2), (solution, namespace2))) || + (namespace2Count > 1 && namespace2.ConstituentNamespaces.Any(static (n2, arg) => OriginalSymbolsMatch(arg.solution, arg.namespace1, n2), (solution, namespace1)))) { return true; } @@ -71,11 +70,8 @@ internal static async Task OriginalSymbolsMatchAsync( return false; } - private static async Task OriginalSymbolsMatchCoreAsync( - Solution solution, - ISymbol searchSymbol, - ISymbol symbolToMatch, - CancellationToken cancellationToken) + private static bool OriginalSymbolsMatchCore( + Solution solution, ISymbol searchSymbol, ISymbol symbolToMatch) { if (searchSymbol == null || symbolToMatch == null) return false; @@ -121,32 +117,22 @@ private static async Task OriginalSymbolsMatchCoreAsync( if (equivalentTypesWithDifferingAssemblies.Count > 0) { // Step 3a) Ensure that all pairs of named types in equivalentTypesWithDifferingAssemblies are indeed equivalent types. - return await VerifyForwardedTypesAsync(solution, equivalentTypesWithDifferingAssemblies, cancellationToken).ConfigureAwait(false); + return VerifyForwardedTypes(solution, equivalentTypesWithDifferingAssemblies); } // 3b) If no such named type pairs were encountered, symbols ARE equivalent. return true; } - private static Task NamespaceSymbolsMatchAsync( - Solution solution, - INamespaceSymbol namespace1, - INamespaceSymbol namespace2, - CancellationToken cancellationToken) - { - return OriginalSymbolsMatchAsync(solution, namespace1, namespace2, cancellationToken); - } - /// /// Verifies that all pairs of named types in equivalentTypesWithDifferingAssemblies are equivalent forwarded types. /// - private static async Task VerifyForwardedTypesAsync( + private static bool VerifyForwardedTypes( Solution solution, - Dictionary equivalentTypesWithDifferingAssemblies, - CancellationToken cancellationToken) + Dictionary equivalentTypesWithDifferingAssemblies) { Contract.ThrowIfNull(equivalentTypesWithDifferingAssemblies); - Contract.ThrowIfTrue(!equivalentTypesWithDifferingAssemblies.Any()); + Contract.ThrowIfTrue(equivalentTypesWithDifferingAssemblies.Count == 0); // Must contain equivalents named types residing in different assemblies. Contract.ThrowIfFalse(equivalentTypesWithDifferingAssemblies.All(kvp => !SymbolEquivalenceComparer.Instance.Equals(kvp.Key.ContainingAssembly, kvp.Value.ContainingAssembly))); @@ -155,16 +141,13 @@ private static async Task VerifyForwardedTypesAsync( Contract.ThrowIfFalse(equivalentTypesWithDifferingAssemblies.All(kvp => kvp.Key.ContainingType == null)); Contract.ThrowIfFalse(equivalentTypesWithDifferingAssemblies.All(kvp => kvp.Value.ContainingType == null)); - // Cache compilations so we avoid recreating any as we walk the pairs of types. - using var _ = PooledHashSet.GetInstance(out var compilationSet); - foreach (var (type1, type2) in equivalentTypesWithDifferingAssemblies) { // Check if type1 was forwarded to type2 in type2's compilation, or if type2 was forwarded to type1 in // type1's compilation. We check both direction as this API is called from higher level comparison APIs // that are unordered. - if (!await VerifyForwardedTypeAsync(solution, candidate: type1, forwardedTo: type2, compilationSet, cancellationToken).ConfigureAwait(false) && - !await VerifyForwardedTypeAsync(solution, candidate: type2, forwardedTo: type1, compilationSet, cancellationToken).ConfigureAwait(false)) + if (!VerifyForwardedType(solution, candidate: type1, forwardedTo: type2) && + !VerifyForwardedType(solution, candidate: type2, forwardedTo: type1)) { return false; } @@ -177,30 +160,20 @@ private static async Task VerifyForwardedTypesAsync( /// Returns if was forwarded to in /// 's . /// - private static async Task VerifyForwardedTypeAsync( + private static bool VerifyForwardedType( Solution solution, INamedTypeSymbol candidate, - INamedTypeSymbol forwardedTo, - HashSet compilationSet, - CancellationToken cancellationToken) + INamedTypeSymbol forwardedTo) { // Only need to operate on original definitions. i.e. List is the type that is forwarded, // not List. candidate = GetOridinalUnderlyingType(candidate); forwardedTo = GetOridinalUnderlyingType(forwardedTo); - var forwardedToOriginatingProject = solution.GetOriginatingProject(forwardedTo); - if (forwardedToOriginatingProject == null) - return false; - - var forwardedToCompilation = await forwardedToOriginatingProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var forwardedToCompilation = solution.GetOriginatingCompilation(forwardedTo); if (forwardedToCompilation == null) return false; - // Cache the compilation so that if we need it while checking another set of forwarded types, we don't - // expensively throw it away and recreate it. - compilationSet.Add(forwardedToCompilation); - var candidateFullMetadataName = candidate.ContainingNamespace?.IsGlobalNamespace != false ? candidate.MetadataName : $"{candidate.ContainingNamespace.ToDisplayString(SymbolDisplayFormats.SignatureFormat)}.{candidate.MetadataName}"; diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs index 1e2473a314f0c..ad70664b74512 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs @@ -54,10 +54,8 @@ internal static async Task> FindOverridesArrayAsync( var sourceMember = await FindSourceDefinitionAsync(m, solution, cancellationToken).ConfigureAwait(false); var bestMember = sourceMember ?? m; - if (await IsOverrideAsync(solution, bestMember, symbol, cancellationToken).ConfigureAwait(false)) - { + if (IsOverride(solution, bestMember, symbol)) results.Add(bestMember); - } } } } @@ -65,11 +63,11 @@ internal static async Task> FindOverridesArrayAsync( return results.ToImmutableAndFree(); } - internal static async Task IsOverrideAsync(Solution solution, ISymbol member, ISymbol symbol, CancellationToken cancellationToken) + internal static bool IsOverride(Solution solution, ISymbol member, ISymbol symbol) { for (var current = member; current != null; current = current.GetOverriddenMember()) { - if (await OriginalSymbolsMatchAsync(solution, current.GetOverriddenMember(), symbol.OriginalDefinition, cancellationToken).ConfigureAwait(false)) + if (OriginalSymbolsMatch(solution, current.GetOverriddenMember(), symbol.OriginalDefinition)) return true; } @@ -133,7 +131,7 @@ internal static async Task> FindImplementedInterfaceMemb var containingType = symbol.ContainingType.OriginalDefinition; var derivedClasses = includeImplementationsThroughDerivedTypes ? await FindDerivedClassesAsync(containingType, solution, projects, cancellationToken).ConfigureAwait(false) - : SpecializedCollections.EmptyEnumerable(); + : []; var allTypes = derivedClasses.Concat(containingType); using var _ = ArrayBuilder.GetInstance(out var builder); @@ -159,8 +157,7 @@ internal static async Task> FindImplementedInterfaceMemb var sourceMethod = await FindSourceDefinitionAsync(interfaceMember, solution, cancellationToken).ConfigureAwait(false); var bestMethod = sourceMethod ?? interfaceMember; - var implementations = await type.FindImplementationsForInterfaceMemberAsync( - bestMethod, solution, cancellationToken).ConfigureAwait(false); + var implementations = type.FindImplementationsForInterfaceMember(bestMethod, solution, cancellationToken); foreach (var implementation in implementations) { if (implementation != null && @@ -361,7 +358,7 @@ internal static async Task> FindMemberImplementationsArr using var _ = ArrayBuilder.GetInstance(out var results); foreach (var t in allTypes) { - var implementations = await t.FindImplementationsForInterfaceMemberAsync(symbol, solution, cancellationToken).ConfigureAwait(false); + var implementations = t.FindImplementationsForInterfaceMember(symbol, solution, cancellationToken); foreach (var implementation in implementations) { var sourceDef = await FindSourceDefinitionAsync(implementation, solution, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index 1a070a690fd63..07564d99b7c1d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -61,11 +61,10 @@ private static async Task LoadOrCreateAsync( var persistentStorageService = services.GetPersistentStorageService(); var storage = await persistentStorageService.GetStorageAsync(solutionKey, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); using (var stream = SerializableBytes.CreateWritableStream()) { - using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) + using (var writer = new ObjectWriter(stream, leaveOpen: true)) { result.WriteTo(writer); } @@ -91,14 +90,13 @@ private static async Task LoadOrCreateAsync( var persistentStorageService = services.GetPersistentStorageService(); var storage = await persistentStorageService.GetStorageAsync(solutionKey, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); // Get the unique key to identify our data. var key = PrefixSymbolTreeInfo + keySuffix; // If the checksum doesn't need to match, then we can pass in 'null' here allowing any result to be found. using var stream = await storage.ReadStreamAsync(key, checksumMustMatch ? checksum : null, cancellationToken).ConfigureAwait(false); - using var reader = ObjectReader.TryGetReader(stream, cancellationToken: cancellationToken); + using var reader = ObjectReader.TryGetReader(stream); // We have some previously persisted data. Attempt to read it back. // If we're able to, and the version of the persisted data matches diff --git a/src/Workspaces/Core/Portable/Formatting/Formatter.cs b/src/Workspaces/Core/Portable/Formatting/Formatter.cs index 77ce8d64b870a..6803e4697a6df 100644 --- a/src/Workspaces/Core/Portable/Formatting/Formatter.cs +++ b/src/Workspaces/Core/Portable/Formatting/Formatter.cs @@ -64,11 +64,11 @@ internal static Task FormatAsync(Document document, SyntaxFormattingOp /// The formatted document. public static Task FormatAsync(Document document, TextSpan span, OptionSet? options = null, CancellationToken cancellationToken = default) #pragma warning disable RS0030 // Do not used banned APIs - => FormatAsync(document, SpecializedCollections.SingletonEnumerable(span), options, cancellationToken); + => FormatAsync(document, [span], options, cancellationToken); #pragma warning restore internal static Task FormatAsync(Document document, TextSpan span, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => FormatAsync(document, SpecializedCollections.SingletonEnumerable(span), options, rules: null, cancellationToken); + => FormatAsync(document, [span], options, rules: null, cancellationToken); /// /// Formats the whitespace in areas of a document corresponding to multiple non-overlapping spans. @@ -187,10 +187,10 @@ internal static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, /// An optional cancellation token. /// The formatted tree's root node. public static SyntaxNode Format(SyntaxNode node, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => Format(node, SpecializedCollections.SingletonEnumerable(node.FullSpan), workspace, options, rules: null, cancellationToken); + => Format(node, [node.FullSpan], workspace, options, rules: null, cancellationToken); internal static SyntaxNode Format(SyntaxNode node, SolutionServices services, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => Format(node, SpecializedCollections.SingletonEnumerable(node.FullSpan), services, options, rules: null, cancellationToken); + => Format(node, [node.FullSpan], services, options, rules: null, cancellationToken); /// /// Formats the whitespace in areas of a syntax tree identified by a span. @@ -202,10 +202,10 @@ internal static SyntaxNode Format(SyntaxNode node, SolutionServices services, Sy /// An optional cancellation token. /// The formatted tree's root node. public static SyntaxNode Format(SyntaxNode node, TextSpan span, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => Format(node, SpecializedCollections.SingletonEnumerable(span), workspace, options, rules: null, cancellationToken: cancellationToken); + => Format(node, [span], workspace, options, rules: null, cancellationToken: cancellationToken); internal static SyntaxNode Format(SyntaxNode node, TextSpan span, SolutionServices services, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => Format(node, SpecializedCollections.SingletonEnumerable(span), services, options, rules: null, cancellationToken: cancellationToken); + => Format(node, [span], services, options, rules: null, cancellationToken: cancellationToken); /// /// Formats the whitespace in areas of a syntax tree identified by multiple non-overlapping spans. @@ -247,7 +247,7 @@ internal static SyntaxNode Format(SyntaxNode node, IEnumerable? spans, return null; } - spans ??= SpecializedCollections.SingletonEnumerable(node.FullSpan); + spans ??= [node.FullSpan]; var formattingOptions = GetFormattingOptions(workspace, options, node.Language); return languageFormatter.GetFormattingResult(node, spans, formattingOptions, rules, cancellationToken); } @@ -267,10 +267,10 @@ internal static IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerab /// An optional cancellation token. /// The changes necessary to format the tree. public static IList GetFormattedTextChanges(SyntaxNode node, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => GetFormattedTextChanges(node, SpecializedCollections.SingletonEnumerable(node.FullSpan), workspace, options, rules: null, cancellationToken: cancellationToken); + => GetFormattedTextChanges(node, [node.FullSpan], workspace, options, rules: null, cancellationToken: cancellationToken); internal static IList GetFormattedTextChanges(SyntaxNode node, SolutionServices services, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => GetFormattedTextChanges(node, SpecializedCollections.SingletonEnumerable(node.FullSpan), services, options, rules: null, cancellationToken: cancellationToken); + => GetFormattedTextChanges(node, [node.FullSpan], services, options, rules: null, cancellationToken: cancellationToken); /// /// Determines the changes necessary to format the whitespace of a syntax tree. @@ -282,10 +282,10 @@ internal static IList GetFormattedTextChanges(SyntaxNode node, Solut /// An optional cancellation token. /// The changes necessary to format the tree. public static IList GetFormattedTextChanges(SyntaxNode node, TextSpan span, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => GetFormattedTextChanges(node, SpecializedCollections.SingletonEnumerable(span), workspace, options, rules: null, cancellationToken); + => GetFormattedTextChanges(node, [span], workspace, options, rules: null, cancellationToken); internal static IList GetFormattedTextChanges(SyntaxNode node, TextSpan span, SolutionServices services, SyntaxFormattingOptions options, CancellationToken cancellationToken = default) - => GetFormattedTextChanges(node, SpecializedCollections.SingletonEnumerable(span), services, options, rules: null, cancellationToken); + => GetFormattedTextChanges(node, [span], services, options, rules: null, cancellationToken); /// /// Determines the changes necessary to format the whitespace of a syntax tree. diff --git a/src/Workspaces/Core/Portable/LanguageServices/FixAllSpanMappingService/AbstractFixAllSpanMappingService.cs b/src/Workspaces/Core/Portable/LanguageServices/FixAllSpanMappingService/AbstractFixAllSpanMappingService.cs index 402e8c5dd9526..570016ae58692 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/FixAllSpanMappingService/AbstractFixAllSpanMappingService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/FixAllSpanMappingService/AbstractFixAllSpanMappingService.cs @@ -37,8 +37,7 @@ private async Task>> GetF if (fixAllInContainingMember) { - return ImmutableDictionary.CreateRange(SpecializedCollections.SingletonEnumerable( - KeyValuePairUtil.Create(document, ImmutableArray.Create(decl.FullSpan)))); + return ImmutableDictionary.CreateRange([KeyValuePairUtil.Create(document, ImmutableArray.Create(decl.FullSpan))]); } else { @@ -64,8 +63,7 @@ private async Task>> GetF } else { - return ImmutableDictionary.CreateRange(SpecializedCollections.SingletonEnumerable( - KeyValuePairUtil.Create(document, ImmutableArray.Create(decl.FullSpan)))); + return ImmutableDictionary.CreateRange([KeyValuePairUtil.Create(document, ImmutableArray.Create(decl.FullSpan))]); } } } diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs index 6603464b13a8e..1f7fb23a853f3 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs @@ -83,7 +83,7 @@ private async Task MergeLinkedDocumentGroupAsync( appliedChanges = await AddDocumentMergeChangesAsync( oldSolution.GetDocument(documentId), newSolution.GetDocument(documentId), - appliedChanges.ToList(), + [.. appliedChanges], unmergedChanges, groupSessionInfo, textDifferencingService, diff --git a/src/Workspaces/Core/Portable/Log/AbstractLogAggregator.cs b/src/Workspaces/Core/Portable/Log/AbstractLogAggregator.cs index 7d53c8ee2b442..993d53f483404 100644 --- a/src/Workspaces/Core/Portable/Log/AbstractLogAggregator.cs +++ b/src/Workspaces/Core/Portable/Log/AbstractLogAggregator.cs @@ -40,7 +40,7 @@ protected AbstractLogAggregator() public void Clear() => _map.Clear(); public IEnumerator> GetEnumerator() - => _map.Select(static kvp => new KeyValuePair((TKey)kvp.Key, kvp.Value)).GetEnumerator(); + => _map.Select(static kvp => KeyValuePairUtil.Create((TKey)kvp.Key, kvp.Value)).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); diff --git a/src/Workspaces/Core/Portable/Log/AggregateLogger.cs b/src/Workspaces/Core/Portable/Log/AggregateLogger.cs index 19a74f1a4972d..6f20609433576 100644 --- a/src/Workspaces/Core/Portable/Log/AggregateLogger.cs +++ b/src/Workspaces/Core/Portable/Log/AggregateLogger.cs @@ -36,7 +36,7 @@ public static AggregateLogger Create(params ILogger[] loggers) set.Add(logger); } - return new AggregateLogger(set.ToImmutableArray()); + return new AggregateLogger([.. set]); } public static ILogger AddOrReplace(ILogger newLogger, ILogger oldLogger, Func predicate) @@ -81,7 +81,7 @@ public static ILogger AddOrReplace(ILogger newLogger, ILogger oldLogger, Func predicate) @@ -105,7 +105,7 @@ public static ILogger Remove(ILogger logger, Func predicate) return set.Single(); } - return new AggregateLogger(set.ToImmutableArray()); + return new AggregateLogger([.. set]); } private AggregateLogger(ImmutableArray loggers) diff --git a/src/Workspaces/Core/Portable/Log/PiiValue.cs b/src/Workspaces/Core/Portable/Log/PiiValue.cs index 667143b2ff4da..720d8afb54efb 100644 --- a/src/Workspaces/Core/Portable/Log/PiiValue.cs +++ b/src/Workspaces/Core/Portable/Log/PiiValue.cs @@ -2,16 +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.Runtime.Serialization; namespace Microsoft.CodeAnalysis.Internal.Log; /// /// Represents telemetry data that's classified as personally identifiable information. /// +[DataContract] internal sealed class PiiValue(object value) { + [DataMember(Order = 0)] public readonly object Value = value; public override string? ToString() diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 556fb9fb07110..2ef8b99744fab 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -105,7 +105,7 @@ - + @@ -131,9 +131,10 @@ - + + diff --git a/src/Workspaces/Core/Portable/ObsoleteSymbol/AbstractObsoleteSymbolService.cs b/src/Workspaces/Core/Portable/ObsoleteSymbol/AbstractObsoleteSymbolService.cs new file mode 100644 index 0000000000000..ed9a0f67a528a --- /dev/null +++ b/src/Workspaces/Core/Portable/ObsoleteSymbol/AbstractObsoleteSymbolService.cs @@ -0,0 +1,213 @@ +// Licensed to the .NET Foundation under one or more 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ObsoleteSymbol; + +internal abstract class AbstractObsoleteSymbolService(int? dimKeywordKind) : IObsoleteSymbolService +{ + /// + /// The of the keyword in Visual Basic, or + /// for C# scenarios. This value is used to improve performance in the token classification + /// fast-path by avoiding unnecessary calls to . + /// + private readonly int? _dimKeywordKind = dimKeywordKind; + + protected virtual void ProcessDimKeyword(ref ArrayBuilder? result, SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) + { + // Take no action by default + } + + public async Task> GetLocationsAsync(Document document, ImmutableArray textSpans, CancellationToken cancellationToken) + { + var syntaxFacts = document.GetRequiredLanguageService(); + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + // Avoid taking a builder from the pool in the common case where there are no references to obsolete symbols + // currently on screen. + ArrayBuilder? result = null; + try + { + foreach (var span in textSpans) + { + Recurse(span, semanticModel); + } + + if (result is null) + return ImmutableArray.Empty; + + result.RemoveDuplicates(); + return result.ToImmutableAndClear(); + } + finally + { + result?.Free(); + } + + void Recurse(TextSpan span, SemanticModel semanticModel) + { + using var _ = 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.TryPop(out var current)) + { + if (current.Span.IntersectsWith(span)) + { + var tokenFromNode = ProcessNode(semanticModel, current); + + foreach (var child in current.ChildNodesAndTokens()) + { + if (child.IsNode) + { + stack.Add(child.AsNode()!); + } + + var token = child.AsToken(); + if (token != tokenFromNode) + ProcessToken(semanticModel, child.AsToken()); + + ExtractStructureFromTrivia(stack, token.LeadingTrivia); + ExtractStructureFromTrivia(stack, token.TrailingTrivia); + } + } + } + } + + static void ExtractStructureFromTrivia(ArrayBuilder stack, SyntaxTriviaList triviaList) + { + foreach (var trivia in triviaList) + { + if (trivia.HasStructure) + { + stack.Add(trivia.GetStructure()!); + } + } + } + + void AddResult(TextSpan span) + { + result ??= ArrayBuilder.GetInstance(); + result.Add(span); + } + + SyntaxToken ProcessNode(SemanticModel semanticModel, SyntaxNode node) + { + if (syntaxFacts.IsUsingAliasDirective(node)) + { + syntaxFacts.GetPartsOfUsingAliasDirective(node, out _, out var aliasToken, out var name); + if (!aliasToken.Span.IsEmpty) + { + // Use 'name.Parent' because VB can't resolve the declared symbol directly from 'node' + var symbol = semanticModel.GetDeclaredSymbol(name.GetRequiredParent(), cancellationToken); + if (IsSymbolObsolete(symbol)) + AddResult(aliasToken.Span); + } + + return aliasToken; + } + else if (syntaxFacts.IsObjectCreationExpression(node)) + { + syntaxFacts.GetPartsOfObjectCreationExpression(node, out var creationKeyword, out _, out _, out _); + if (!creationKeyword.Span.IsEmpty) + { + // For syntax like the following + // + // SomeType value = new SomeType(); + // + // We classify 'new' as obsolete only if the specific constructor is obsolete. If the containing + // type is obsolete, the classification will be applied to 'SomeType' instead. + var symbol = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol; + if (IsSymbolObsolete(symbol)) + AddResult(creationKeyword.Span); + } + } + else if (syntaxFacts.IsImplicitObjectCreationExpression(node)) + { + syntaxFacts.GetPartsOfImplicitObjectCreationExpression(node, out var creationKeyword, out _, out _); + if (!creationKeyword.Span.IsEmpty) + { + // For syntax like the following + // + // SomeType value = new(); + // + // We classify 'new' as obsolete if either the type or the specific constructor is obsolete. + var symbol = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol; + if (IsSymbolObsolete(symbol) || IsSymbolObsolete(symbol?.ContainingType)) + AddResult(creationKeyword.Span); + } + } + + return default; + } + + void ProcessToken(SemanticModel semanticModel, SyntaxToken token) + { + if (syntaxFacts.IsIdentifier(token)) + { + ProcessIdentifier(semanticModel, token); + } + else if (token.RawKind == _dimKeywordKind) + { + ProcessDimKeyword(ref result, semanticModel, token, cancellationToken); + } + } + + void ProcessIdentifier(SemanticModel semanticModel, SyntaxToken token) + { + if (syntaxFacts.IsDeclaration(token.Parent)) + { + var symbol = semanticModel.GetDeclaredSymbol(token.Parent, cancellationToken); + if (IsSymbolObsolete(symbol)) + AddResult(token.Span); + } + else + { + var symbol = semanticModel.GetSymbolInfo(token, cancellationToken).Symbol; + if (IsSymbolObsolete(symbol)) + AddResult(token.Span); + } + } + } + + protected static bool IsSymbolObsolete([NotNullWhen(true)] ISymbol? symbol) + { + // Avoid infinite recursion. Iteration limit chosen arbitrarily; cases are generally expected to complete on + // the first iteration or fail completely. + for (var i = 0; i < 5; i++) + { + if (symbol is IAliasSymbol alias) + { + symbol = alias.Target; + continue; + } + + if (symbol is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T, TypeArguments: [var valueType] }) + { + symbol = valueType; + continue; + } + + return symbol?.IsObsolete() ?? false; + } + + // Unable to determine whether the symbol is considered obsolete + return false; + } +} diff --git a/src/Workspaces/Core/Portable/ObsoleteSymbol/IObsoleteSymbolService.cs b/src/Workspaces/Core/Portable/ObsoleteSymbol/IObsoleteSymbolService.cs new file mode 100644 index 0000000000000..7e1d65363baff --- /dev/null +++ b/src/Workspaces/Core/Portable/ObsoleteSymbol/IObsoleteSymbolService.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more 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.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ObsoleteSymbol; + +/// +/// Service which can analyze a span of a document and identify all locations of declarations or references to +/// symbols which are marked . +/// +internal interface IObsoleteSymbolService : ILanguageService +{ + Task> GetLocationsAsync(Document document, ImmutableArray textSpans, CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Core/Portable/ReassignedVariable/AbstractReassignedVariableService.cs b/src/Workspaces/Core/Portable/ReassignedVariable/AbstractReassignedVariableService.cs index 86bacec0c6c90..2393e5dff2ef2 100644 --- a/src/Workspaces/Core/Portable/ReassignedVariable/AbstractReassignedVariableService.cs +++ b/src/Workspaces/Core/Portable/ReassignedVariable/AbstractReassignedVariableService.cs @@ -56,7 +56,7 @@ public async Task> GetLocationsAsync( } result.RemoveDuplicates(); - return result.ToImmutable(); + return result.ToImmutableAndClear(); void Recurse(TextSpan span, SemanticModel semanticModel) { diff --git a/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs b/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs index 7e259990d0df9..b861712b760b8 100644 --- a/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs +++ b/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs @@ -170,7 +170,7 @@ private ImmutableArray SubstituteTypeParameters(ImmutableArray @@ -422,7 +422,7 @@ protected ImmutableArray LookupSymbolsInContainer( result.Add(member); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); static bool MatchesConstraints(ITypeSymbol originalContainerType, ImmutableArray constraintTypes) { @@ -455,21 +455,45 @@ static bool MatchesConstraint(ITypeSymbol originalContainerType, ITypeSymbol ori } else if (originalConstraintType.TypeKind == TypeKind.Interface) { - // If the constraint is an interface then see if that interface appears in the interface inheritance - // hierarchy of the type we're dotting off of. - foreach (var interfaceType in originalContainerType.AllInterfaces) + if (originalContainerType is ITypeParameterSymbol typeParameterContainer) { - if (SymbolEqualityComparer.Default.Equals(interfaceType.OriginalDefinition, originalConstraintType)) - return true; + // If the container type is a type parameter, we attempt to match all the interfaces from its constraint types. + foreach (var constraintType in typeParameterContainer.ConstraintTypes) + { + foreach (var constraintTypeInterface in constraintType.GetAllInterfacesIncludingThis()) + { + if (SymbolEqualityComparer.Default.Equals(constraintTypeInterface.OriginalDefinition, originalConstraintType)) + return true; + } + } + } + else + { + // If the constraint is an interface then see if that interface appears in the interface inheritance + // hierarchy of the type we're dotting off of. + foreach (var interfaceType in originalContainerType.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(interfaceType.OriginalDefinition, originalConstraintType)) + return true; + } } } else if (originalConstraintType.TypeKind == TypeKind.Class) { - // If the constraint is an interface then see if that interface appears in the base type inheritance - // hierarchy of the type we're dotting off of. - for (var current = originalContainerType.BaseType; current != null; current = current.BaseType) + if (originalContainerType is ITypeParameterSymbol typeParameterContainer) { - if (SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, originalConstraintType)) + // If the container type is a type parameter, we iterate through all the type's constrained types. + foreach (var constrainedType in typeParameterContainer.ConstraintTypes) + { + if (MatchesAnyBaseTypes(constrainedType, originalConstraintType)) + return true; + } + } + else + { + // If the constraint is an interface then see if that interface appears in the base type inheritance + // hierarchy of the type we're dotting off of. + if (MatchesAnyBaseTypes(originalContainerType.BaseType, originalConstraintType)) return true; } } @@ -483,6 +507,17 @@ static bool MatchesConstraint(ITypeSymbol originalContainerType, ITypeSymbol ori // For anything else, we don't consider this a match. This can be adjusted in the future if need be. return false; + + static bool MatchesAnyBaseTypes(ITypeSymbol source, ITypeSymbol matched) + { + for (var current = source; current != null; current = current.BaseType) + { + if (SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, matched)) + return true; + } + + return false; + } } } diff --git a/src/Workspaces/Core/Portable/Remote/IRemoteKeepAliveService.cs b/src/Workspaces/Core/Portable/Remote/IRemoteKeepAliveService.cs index f3e0de1e413db..024db2a78d641 100644 --- a/src/Workspaces/Core/Portable/Remote/IRemoteKeepAliveService.cs +++ b/src/Workspaces/Core/Portable/Remote/IRemoteKeepAliveService.cs @@ -26,6 +26,21 @@ internal sealed class RemoteKeepAliveSession : IDisposable { private readonly CancellationTokenSource _cancellationTokenSource = new(); + private RemoteKeepAliveSession( + SolutionCompilationState compilationState, + RemoteHostClient? client) + { + if (client is null) + return; + + // Now kick off the keep-alive work. We don't wait on this as this will stick on the OOP side until + // the cancellation token triggers. + _ = client.TryInvokeAsync( + compilationState, + (service, solutionInfo, cancellationToken) => service.KeepAliveAsync(solutionInfo, cancellationToken), + _cancellationTokenSource.Token).AsTask(); + } + private RemoteKeepAliveSession(SolutionCompilationState compilationState, IAsynchronousOperationListener listener) { var cancellationToken = _cancellationTokenSource.Token; @@ -44,7 +59,7 @@ async Task CreateClientAndKeepAliveAsync() // Now kick off the keep-alive work. We don't wait on this as this will stick on the OOP side until // the cancellation token triggers. - var unused = client.TryInvokeAsync( + _ = client.TryInvokeAsync( compilationState, (service, solutionInfo, cancellationToken) => service.KeepAliveAsync(solutionInfo, cancellationToken), cancellationToken).AsTask(); @@ -78,13 +93,27 @@ public void Dispose() /// system know when unobserved async work is kicked off in case we have any tooling that keep track of this for /// any reason (for example for tracking down problems in testing scenarios). /// + /// + /// This synchronous entrypoint should be used only in contexts where using the async is not possible (for example, in a constructor). + /// public static RemoteKeepAliveSession Create(Solution solution, IAsynchronousOperationListener listener) - => Create(solution.CompilationState, listener); + => new(solution.CompilationState, listener); + + /// + /// Creates a session between the host and OOP, effectively pinning this until is called on it. By pinning the solution we ensure that all calls to OOP for + /// the same solution during the life of this session do not need to resync the solution. Nor do they then need + /// to rebuild any compilations they've already built due to the solution going away and then coming back. + /// + public static Task CreateAsync(Solution solution, CancellationToken cancellationToken) + => CreateAsync(solution.CompilationState, cancellationToken); - /// - public static RemoteKeepAliveSession Create( - SolutionCompilationState compilationState, IAsynchronousOperationListener listener) + /// + public static async Task CreateAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { - return new RemoteKeepAliveSession(compilationState, listener); + var client = await RemoteHostClient.TryGetClientAsync(compilationState.Services, cancellationToken).ConfigureAwait(false); + return new RemoteKeepAliveSession(compilationState, client); } } diff --git a/src/Workspaces/Core/Portable/Remote/ISerializerService.cs b/src/Workspaces/Core/Portable/Remote/ISerializerService.cs index f6f74f5bae648..f56f5f0a89417 100644 --- a/src/Workspaces/Core/Portable/Remote/ISerializerService.cs +++ b/src/Workspaces/Core/Portable/Remote/ISerializerService.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Serialization; internal interface ISerializerService : IWorkspaceService { - void Serialize(object value, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken); + void Serialize(object value, ObjectWriter writer, CancellationToken cancellationToken); void SerializeParseOptions(ParseOptions options, ObjectWriter writer); diff --git a/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs b/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs index 5542cc7fbcb51..4f8bb3b9ddc17 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs @@ -151,6 +151,17 @@ public async ValueTask TryInvokeAsync( return await connection.TryInvokeAsync(project, invocation, cancellationToken).ConfigureAwait(false); } + public async ValueTask> TryInvokeAsync( + SolutionCompilationState compilationState, + ProjectId projectId, + Func> invocation, + CancellationToken cancellationToken) + where TService : class + { + using var connection = CreateConnection(callbackTarget: null); + return await connection.TryInvokeAsync(compilationState, projectId, invocation, cancellationToken).ConfigureAwait(false); + } + /// /// Equivalent to /// except that only the project (and its dependent projects) will be sync'ed to the remote host before executing. diff --git a/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs b/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs index b6e0f76c3d498..31746fb54306c 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs @@ -39,7 +39,7 @@ internal static class RemoteUtilities } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } /// diff --git a/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs b/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs index 3b58dc9d74380..a629713625af6 100644 --- a/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs +++ b/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs @@ -4,7 +4,7 @@ namespace Microsoft.CodeAnalysis.Serialization; -internal enum WellKnownSynchronizationKind +internal enum WellKnownSynchronizationKind : byte { // Start at a different value from 0 so that if we ever get 0 we know it's a bug. @@ -12,23 +12,20 @@ internal enum WellKnownSynchronizationKind SolutionCompilationState = 1, // Solution snapshot state, only referencing actual user (non-generated) documents, options, and references. - SolutionState, - ProjectState, - DocumentState, - - ChecksumCollection, - - SolutionAttributes, - ProjectAttributes, - DocumentAttributes, - SourceGeneratedDocumentIdentity, - - CompilationOptions, - ParseOptions, - ProjectReference, - MetadataReference, - AnalyzerReference, - SourceText, - - SerializableSourceText, + SolutionState = 2, + ProjectState = 3, + + SolutionAttributes = 4, + ProjectAttributes = 5, + DocumentAttributes = 6, + SourceGeneratedDocumentIdentity = 7, + SourceGeneratorExecutionVersionMap = 8, + + CompilationOptions = 9, + ParseOptions = 10, + ProjectReference = 11, + MetadataReference = 12, + AnalyzerReference = 13, + + SerializableSourceText = 14, } diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs index 34190d5261e2c..0a0b5e3c63411 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs @@ -177,10 +177,13 @@ public async Task ResolveConflictsAsync() if (phase == 1) { - conflictLocations = conflictLocations.Concat(conflictResolution.RelatedLocations - .Where(loc => documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict) - .Select(loc => new ConflictLocationInfo(loc))) - .ToImmutableHashSet(); + conflictLocations = + [ + .. conflictLocations, + .. conflictResolution.RelatedLocations + .Where(loc => documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict) + .Select(loc => new ConflictLocationInfo(loc)), + ]; } // Set the documents with conflicts that need to be processed in the next phase. diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/MutableConflictResolution.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/MutableConflictResolution.cs index 39cc3f28a0792..61ccd36af02b5 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/MutableConflictResolution.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/MutableConflictResolution.cs @@ -80,7 +80,7 @@ internal async Task RemoveAllRenameAnnotationsAsync( computeReplacementNode: (original, updated) => annotationSet.WithoutAnnotations(updated, annotationSet.GetAnnotations(updated).ToArray()), tokens: annotationSet.GetAnnotatedTokens(root), computeReplacementToken: (original, updated) => annotationSet.WithoutAnnotations(updated, annotationSet.GetAnnotations(updated).ToArray()), - trivia: SpecializedCollections.EmptyEnumerable(), + trivia: [], computeReplacementTrivia: null); intermediateSolution = intermediateSolution.WithDocumentSyntaxRoot(documentId, newRoot, PreservationMode.PreserveIdentity); diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs index dbaa6671a11e1..7a71e5dd2b9a3 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs @@ -55,7 +55,7 @@ internal int GetAdjustedPosition(int startingPosition, DocumentId documentId) { var documentReplacementSpans = _documentToModifiedSpansMap.TryGetValue(documentId, out var modifiedSpans) ? modifiedSpans.Where(pair => pair.oldSpan.Start < startingPosition) - : SpecializedCollections.EmptyEnumerable<(TextSpan oldSpan, TextSpan newSpan)>(); + : []; var adjustedStartingPosition = startingPosition; foreach (var (oldSpan, newSpan) in documentReplacementSpans) @@ -65,7 +65,7 @@ internal int GetAdjustedPosition(int startingPosition, DocumentId documentId) var documentComplexifiedSpans = _documentToComplexifiedSpansMap.TryGetValue(documentId, out var complexifiedSpans) ? complexifiedSpans.Where(c => c.OriginalSpan.Start <= startingPosition) - : SpecializedCollections.EmptyEnumerable(); + : []; var appliedTextSpans = new HashSet(); foreach (var c in documentComplexifiedSpans.Reverse()) @@ -226,7 +226,7 @@ internal async Task SimplifyAsync(Solution solution, IEnumerable>(); foreach (var (docId, spans) in _documentToModifiedSpansMap) - builder.Add(docId, spans.ToImmutableArray()); + builder.Add(docId, [.. spans]); return builder.ToImmutable(); } @@ -238,7 +238,7 @@ public ImmutableDictionary> GetDocu foreach (var (docId, spans) in _documentToComplexifiedSpansMap) { builder.Add(docId, spans.SelectAsArray( - s => new ComplexifiedSpan(s.OriginalSpan, s.NewSpan, s.ModifiedSubSpans.ToImmutableArray()))); + s => new ComplexifiedSpan(s.OriginalSpan, s.NewSpan, [.. s.ModifiedSubSpans]))); } return builder.ToImmutable(); diff --git a/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs b/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs index da46de7a1ca93..f5385ee1c6ed9 100644 --- a/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs +++ b/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs @@ -185,11 +185,11 @@ internal sealed class SerializableRenameLocations( public async ValueTask> RehydrateLocationsAsync( Solution solution, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(this.Locations.Length, out var locBuilder); + var locBuilder = new FixedSizeArrayBuilder(this.Locations.Length); foreach (var loc in this.Locations) locBuilder.Add(await loc.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false)); - return locBuilder.ToImmutableAndClear(); + return locBuilder.MoveToImmutable(); } } diff --git a/src/Workspaces/Core/Portable/Rename/Renamer.cs b/src/Workspaces/Core/Portable/Rename/Renamer.cs index 741bab2bd9cfa..cc3c90299437b 100644 --- a/src/Workspaces/Core/Portable/Rename/Renamer.cs +++ b/src/Workspaces/Core/Portable/Rename/Renamer.cs @@ -119,7 +119,7 @@ internal static async Task RenameDocumentAsync( if (document.Services.GetService() != null) { // Don't advertise that we can file rename generated documents that map to a different file. - return new RenameDocumentActionSet([], document.Id, document.Name, document.Folders.ToImmutableArray(), options); + return new RenameDocumentActionSet([], document.Id, document.Name, [.. document.Folders], options); } using var _ = ArrayBuilder.GetInstance(out var actions); @@ -143,7 +143,7 @@ internal static async Task RenameDocumentAsync( actions.ToImmutable(), document.Id, newDocumentName, - newDocumentFolders.ToImmutableArray(), + [.. newDocumentFolders], options); } diff --git a/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.ReferenceProcessing.cs b/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.ReferenceProcessing.cs index 85a3daef15c1e..120c21c7ec2ae 100644 --- a/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.ReferenceProcessing.cs +++ b/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.ReferenceProcessing.cs @@ -235,22 +235,16 @@ internal static async Task> GetRenamableReferenceLoc // We won't try to update references in source generated files; we'll assume the generator will rerun // and produce an updated document with the new name. if (location.Document is SourceGeneratedDocument) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var shouldIncludeSymbol = await ShouldIncludeSymbolAsync(referencedSymbol, originalSymbol, solution, true, cancellationToken).ConfigureAwait(false); if (!shouldIncludeSymbol) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; // Implicit references are things like a foreach referencing GetEnumerator. We don't // want to consider those as part of the set if (location.IsImplicit) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var results = new List(); diff --git a/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.cs b/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.cs index 6bd6aa32dfe2b..9686f68c85235 100644 --- a/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.cs +++ b/src/Workspaces/Core/Portable/Rename/SymbolicRenameLocations.cs @@ -123,7 +123,7 @@ private static async Task> GetOverloadsAsync( foreach (var overloadedSymbol in RenameUtilities.GetOverloadedSymbols(symbol)) overloadsResult.Add(await AddLocationsReferenceSymbolsAsync(overloadedSymbol, solution, cancellationToken).ConfigureAwait(false)); - return overloadsResult.ToImmutable(); + return overloadsResult.ToImmutableAndClear(); } private static async Task AddLocationsReferenceSymbolsAsync( diff --git a/src/Workspaces/Core/Portable/Rename/TokenRenameInfo.cs b/src/Workspaces/Core/Portable/Rename/TokenRenameInfo.cs index 79385ad17a122..378a54d1a679a 100644 --- a/src/Workspaces/Core/Portable/Rename/TokenRenameInfo.cs +++ b/src/Workspaces/Core/Portable/Rename/TokenRenameInfo.cs @@ -29,7 +29,7 @@ public static TokenRenameInfo CreateSingleSymbolTokenInfo(ISymbol symbol) ( hasSymbols: true, isMemberGroup: false, - symbols: SpecializedCollections.SingletonEnumerable(symbol) + symbols: [symbol] ); } @@ -37,6 +37,6 @@ public static TokenRenameInfo CreateSingleSymbolTokenInfo(ISymbol symbol) ( hasSymbols: false, isMemberGroup: false, - symbols: SpecializedCollections.EmptyEnumerable() + symbols: [] ); } diff --git a/src/Workspaces/Core/Portable/Serialization/AbstractOptionsSerializationService.cs b/src/Workspaces/Core/Portable/Serialization/AbstractOptionsSerializationService.cs index 3a296dba72ee3..ef1ff19c1258e 100644 --- a/src/Workspaces/Core/Portable/Serialization/AbstractOptionsSerializationService.cs +++ b/src/Workspaces/Core/Portable/Serialization/AbstractOptionsSerializationService.cs @@ -146,7 +146,7 @@ protected static ( } } - var specificDiagnosticOptions = specificDiagnosticOptionsList ?? SpecializedCollections.EmptyEnumerable>(); + var specificDiagnosticOptions = specificDiagnosticOptionsList ?? []; var concurrentBuild = reader.ReadBoolean(); var deterministic = reader.ReadBoolean(); @@ -232,7 +232,7 @@ protected static (SourceCodeKind kind, DocumentationMode documentationMode, IEnu } } - var features = featuresList ?? SpecializedCollections.EmptyEnumerable>(); + var features = featuresList ?? []; return (kind, documentationMode, features); } } diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index e17bcb1a26d4f..a7c02bba2bfc5 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -8,10 +8,11 @@ namespace Microsoft.CodeAnalysis.Serialization; /// -/// This lets consumer to get to inner temporary storage that references use -/// as its shadow copy storage +/// Interface for services that support dumping their contents to memory-mapped-files (generally speaking, our assembly +/// reference objects). This allows those objects to expose the memory-mapped-file info needed to read that data back +/// in in any process. /// internal interface ISupportTemporaryStorage { - IReadOnlyList? GetStorages(); + IReadOnlyList? StorageHandles { get; } } diff --git a/src/Workspaces/Core/Portable/Serialization/PooledList.cs b/src/Workspaces/Core/Portable/Serialization/PooledList.cs deleted file mode 100644 index 672612efae0a6..0000000000000 --- a/src/Workspaces/Core/Portable/Serialization/PooledList.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Collections.Generic; - -namespace Microsoft.CodeAnalysis.Serialization; - -/// -/// This is just internal utility type to reduce allocations and redundant code -/// -internal static class Creator -{ - public static PooledObject> CreateChecksumSet(ReadOnlyMemory checksums) - { - var items = SharedPools.Default>().GetPooledObject(); - - var hashSet = items.Object; - foreach (var checksum in checksums.Span) - hashSet.Add(checksum); - - return items; - } - - public static PooledObject> CreateChecksumSet(Checksum checksum) - { - var items = SharedPools.Default>().GetPooledObject(); - items.Object.Add(checksum); - return items; - } - - public static PooledObject> CreateList() - => SharedPools.Default>().GetPooledObject(); - - public static PooledObject> CreateResultMap(out Dictionary result) - { - var pooled = SharedPools.Default>().GetPooledObject(); - result = pooled.Object; - return pooled; - } -} diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 813c56037b114..06e2b900e3035 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -5,16 +5,23 @@ using System; using System.Collections.Immutable; using System.Diagnostics; -using System.Text; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.Host.TemporaryStorageService; + +#if DEBUG +using System.Linq; +#endif namespace Microsoft.CodeAnalysis.Serialization; +#pragma warning disable CA1416 // Validate platform compatibility + /// /// Represents a which can be serialized for sending to another process. The text is not /// required to be a live object in the current process, and can instead be held in temporary storage accessible by @@ -26,40 +33,51 @@ internal sealed class SerializableSourceText /// The storage location for . /// /// - /// Exactly one of or will be non-. + /// Exactly one of or will be non-. /// - private readonly ITemporaryTextStorageWithName? _storage; + private readonly TemporaryStorageTextHandle? _storageHandle; /// /// The in the current process. /// /// - /// + /// /// private readonly SourceText? _text; /// - /// Weak reference to a SourceText computed from . Useful so that if multiple requests + /// Weak reference to a SourceText computed from . Useful so that if multiple requests /// come in for the source text, the same one can be returned as long as something is holding it alive. /// private readonly WeakReference _computedText = new(target: null); - public SerializableSourceText(ITemporaryTextStorageWithName storage) - : this(storage, text: null) + /// + /// Checksum of the contents (see ) of the text. + /// + public readonly Checksum ContentChecksum; + + public SerializableSourceText(TemporaryStorageTextHandle storageHandle) + : this(storageHandle, text: null, storageHandle.ContentHash) { } - public SerializableSourceText(SourceText text) - : this(storage: null, text) + public SerializableSourceText(SourceText text, ImmutableArray contentHash) + : this(storageHandle: null, text, contentHash) { } - private SerializableSourceText(ITemporaryTextStorageWithName? storage, SourceText? text) + private SerializableSourceText(TemporaryStorageTextHandle? storageHandle, SourceText? text, ImmutableArray contentHash) { - Debug.Assert(storage is null != text is null); + Debug.Assert(storageHandle is null != text is null); - _storage = storage; + _storageHandle = storageHandle; _text = text; + ContentChecksum = Checksum.Create(contentHash); + +#if DEBUG + var computedContentHash = TryGetText()?.GetContentHash() ?? _storageHandle!.ContentHash; + Debug.Assert(contentHash.SequenceEqual(computedContentHash)); +#endif } /// @@ -70,11 +88,6 @@ private SerializableSourceText(ITemporaryTextStorageWithName? storage, SourceTex private SourceText? TryGetText() => _text ?? _computedText.GetTarget(); - public ImmutableArray GetContentHash() - { - return TryGetText()?.GetContentHash() ?? _storage!.GetContentHash(); - } - public async ValueTask GetTextAsync(CancellationToken cancellationToken) { var text = TryGetText(); @@ -82,7 +95,7 @@ public async ValueTask GetTextAsync(CancellationToken cancellationTo return text; // Read and cache the text from the storage object so that other requests may see it if still kept alive by something. - text = await _storage!.ReadTextAsync(cancellationToken).ConfigureAwait(false); + text = await _storageHandle!.ReadFromTemporaryStorageAsync(cancellationToken).ConfigureAwait(false); _computedText.SetTarget(text); return text; } @@ -94,88 +107,136 @@ public SourceText GetText(CancellationToken cancellationToken) return text; // Read and cache the text from the storage object so that other requests may see it if still kept alive by something. - text = _storage!.ReadText(cancellationToken); + text = _storageHandle!.ReadFromTemporaryStorage(cancellationToken); _computedText.SetTarget(text); return text; } - public static ValueTask FromTextDocumentStateAsync(TextDocumentState state, CancellationToken cancellationToken) + public static ValueTask FromTextDocumentStateAsync( + TextDocumentState state, CancellationToken cancellationToken) { - if (state.Storage is ITemporaryTextStorageWithName storage) + if (state.TextAndVersionSource.TextLoader is SerializableSourceTextLoader serializableLoader) { - return new ValueTask(new SerializableSourceText(storage)); + // If we're already pointing at a serializable loader, we can just use that directly. + return new(serializableLoader.SerializableSourceText); + } + else if (state.StorageHandle is TemporaryStorageTextHandle storageHandle) + { + // Otherwise, if we're pointing at a memory mapped storage location, we can create the source text that directly wraps that. + return new(new SerializableSourceText(storageHandle)); } else { + // Otherwise, the state object has reified the text into some other form, and dumped any original + // information on how it got it. In that case, we create a new text instance to represent the serializable + // source text out of. + return SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync( static (state, cancellationToken) => state.GetTextAsync(cancellationToken), - static (text, _) => new SerializableSourceText(text), + static (text, _) => new SerializableSourceText(text, text.GetContentHash()), state, cancellationToken); } } - public void Serialize(ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public void Serialize(ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (_storage is not null) - { - context.AddResource(_storage); - - writer.WriteInt32((int)_storage.ChecksumAlgorithm); - writer.WriteEncoding(_storage.Encoding); + if (_storageHandle is not null) + { writer.WriteInt32((int)SerializationKinds.MemoryMapFile); - writer.WriteString(_storage.Name); - writer.WriteInt64(_storage.Offset); - writer.WriteInt64(_storage.Size); + _storageHandle.Identifier.WriteTo(writer); + writer.WriteInt32((int)_storageHandle.ChecksumAlgorithm); + writer.WriteEncoding(_storageHandle.Encoding); + writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_storageHandle.ContentHash)!); } else { RoslynDebug.AssertNotNull(_text); + writer.WriteInt32((int)SerializationKinds.Bits); writer.WriteInt32((int)_text.ChecksumAlgorithm); writer.WriteEncoding(_text.Encoding); - writer.WriteInt32((int)SerializationKinds.Bits); + writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_text.GetContentHash())!); + _text.WriteTo(writer, cancellationToken); } } public static SerializableSourceText Deserialize( ObjectReader reader, - ITemporaryStorageServiceInternal storageService, + TemporaryStorageService storageService, ITextFactoryService textService, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); - var encoding = reader.ReadEncoding(); - var kind = (SerializationKinds)reader.ReadInt32(); Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); if (kind == SerializationKinds.MemoryMapFile) { - var storage2 = (ITemporaryStorageService2)storageService; - - var name = reader.ReadRequiredString(); - var offset = reader.ReadInt64(); - var size = reader.ReadInt64(); - - var storage = storage2.AttachTemporaryTextStorage(name, offset, size, checksumAlgorithm, encoding); - if (storage is ITemporaryTextStorageWithName storageWithName) - { - return new SerializableSourceText(storageWithName); - } - else - { - return new SerializableSourceText(storage.ReadText(cancellationToken)); - } + var identifier = TemporaryStorageIdentifier.ReadFrom(reader); + var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); + var encoding = reader.ReadEncoding(); + var contentHash = ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray()); + var storageHandle = storageService.GetTextHandle(identifier, checksumAlgorithm, encoding, contentHash); + + return new SerializableSourceText(storageHandle); } else { - return new SerializableSourceText(SourceTextExtensions.ReadFrom(textService, reader, encoding, checksumAlgorithm, cancellationToken)); + var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); + var encoding = reader.ReadEncoding(); + var contentHash = ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray()); + + return new SerializableSourceText( + SourceTextExtensions.ReadFrom(textService, reader, encoding, checksumAlgorithm, cancellationToken), + contentHash); } } + + public TextLoader ToTextLoader(string? filePath) + => new SerializableSourceTextLoader(this, filePath); + + /// + /// A that wraps a and provides access to the text in + /// a deferred fashion. In practice, during a host and OOP sync, while all the documents will be 'serialized' over + /// to OOP, the actual contents of the documents will only need to be loaded depending on which files are open, and + /// thus what compilations and trees are needed. As such, we want to be able to lazily defer actually getting the + /// contents of the text until it's actually needed. This loader allows us to do that, allowing the OOP side to + /// simply point to the segments in the memory-mapped-file the host has dumped its text into, and only actually + /// realizing the real text values when they're needed. + /// + private sealed class SerializableSourceTextLoader : TextLoader + { + public readonly SerializableSourceText SerializableSourceText; + private readonly VersionStamp _version = VersionStamp.Create(); + + public SerializableSourceTextLoader( + SerializableSourceText serializableSourceText, + string? filePath) + { + SerializableSourceText = serializableSourceText; + FilePath = filePath; + } + + internal override string? FilePath { get; } + + /// + /// Documents should always hold onto instances of this text loader strongly. In other words, they should load + /// from this, and then dump the contents into a RecoverableText object that then dumps the contents to a memory + /// mapped file within this process. Doing that is pointless as the contents of this text are already in a + /// memory mapped file on the host side. + /// + internal override bool AlwaysHoldStrongly + => true; + + public override async Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) + => TextAndVersion.Create(await this.SerializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false), _version); + + internal override TextAndVersion LoadTextAndVersionSynchronously(LoadTextOptions options, CancellationToken cancellationToken) + => TextAndVersion.Create(this.SerializableSourceText.GetText(cancellationToken), _version); + } } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs b/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs index 76aa8fd1e4438..3a313639e206a 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs @@ -6,7 +6,6 @@ using System.IO; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Serialization; @@ -19,8 +18,6 @@ public static WellKnownSynchronizationKind GetWellKnownSynchronizationKind(this SolutionCompilationStateChecksums => WellKnownSynchronizationKind.SolutionCompilationState, SolutionStateChecksums => WellKnownSynchronizationKind.SolutionState, ProjectStateChecksums => WellKnownSynchronizationKind.ProjectState, - DocumentStateChecksums => WellKnownSynchronizationKind.DocumentState, - ChecksumCollection => WellKnownSynchronizationKind.ChecksumCollection, SolutionInfo.SolutionAttributes => WellKnownSynchronizationKind.SolutionAttributes, ProjectInfo.ProjectAttributes => WellKnownSynchronizationKind.ProjectAttributes, DocumentInfo.DocumentAttributes => WellKnownSynchronizationKind.DocumentAttributes, @@ -30,8 +27,8 @@ public static WellKnownSynchronizationKind GetWellKnownSynchronizationKind(this MetadataReference => WellKnownSynchronizationKind.MetadataReference, AnalyzerReference => WellKnownSynchronizationKind.AnalyzerReference, SerializableSourceText => WellKnownSynchronizationKind.SerializableSourceText, - SourceText => WellKnownSynchronizationKind.SourceText, SourceGeneratedDocumentIdentity => WellKnownSynchronizationKind.SourceGeneratedDocumentIdentity, + SourceGeneratorExecutionVersionMap => WellKnownSynchronizationKind.SourceGeneratorExecutionVersionMap, _ => throw ExceptionUtilities.UnexpectedValue(value), }; diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 33ce36e947f2a..e786280b8b51c 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -6,16 +6,19 @@ using System.Collections.Concurrent; using System.Composition; using System.Linq; +using System.Runtime.Versioning; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Serialization; +#if NETCOREAPP +[SupportedOSPlatform("windows")] +#endif internal partial class SerializerService : ISerializerService { [ExportWorkspaceServiceFactory(typeof(ISerializerService), layer: ServiceLayer.Default), Shared] @@ -36,7 +39,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) private readonly SolutionServices _workspaceServices; - private readonly ITemporaryStorageServiceInternal _storageService; + private readonly TemporaryStorageService _storageService; private readonly ITextFactoryService _textService; private readonly IDocumentationProviderService? _documentationService; private readonly IAnalyzerAssemblyLoaderProvider _analyzerLoaderProvider; @@ -48,7 +51,9 @@ private protected SerializerService(SolutionServices workspaceServices) { _workspaceServices = workspaceServices; - _storageService = workspaceServices.GetRequiredService(); + // Serialization is only involved when we have a remote process. Which is only in VS. So the type of the + // storage service here is well known. + _storageService = (TemporaryStorageService)workspaceServices.GetRequiredService(); _textService = workspaceServices.GetRequiredService(); _analyzerLoaderProvider = workspaceServices.GetRequiredService(); _documentationService = workspaceServices.GetService(); @@ -75,7 +80,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken case WellKnownSynchronizationKind.ParseOptions: case WellKnownSynchronizationKind.ProjectReference: case WellKnownSynchronizationKind.SourceGeneratedDocumentIdentity: - return Checksum.Create(value, this); + return Checksum.Create(value, this, cancellationToken); case WellKnownSynchronizationKind.MetadataReference: return CreateChecksum((MetadataReference)value, cancellationToken); @@ -84,10 +89,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken return CreateChecksum((AnalyzerReference)value, cancellationToken); case WellKnownSynchronizationKind.SerializableSourceText: - return Checksum.Create(((SerializableSourceText)value).GetContentHash()); - - case WellKnownSynchronizationKind.SourceText: - return Checksum.Create(((SourceText)value).GetContentHash()); + throw new InvalidOperationException("Clients can already get a checksum directly from a SerializableSourceText"); default: // object that is not part of solution is not supported since we don't know what inputs are required to @@ -97,7 +99,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken } } - public void Serialize(object value, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public void Serialize(object value, ObjectWriter writer, CancellationToken cancellationToken) { var kind = value.GetWellKnownSynchronizationKind(); @@ -137,7 +139,7 @@ public void Serialize(object value, ObjectWriter writer, SolutionReplicationCont return; case WellKnownSynchronizationKind.MetadataReference: - SerializeMetadataReference((MetadataReference)value, writer, context, cancellationToken); + SerializeMetadataReference((MetadataReference)value, writer, cancellationToken); return; case WellKnownSynchronizationKind.AnalyzerReference: @@ -145,11 +147,7 @@ public void Serialize(object value, ObjectWriter writer, SolutionReplicationCont return; case WellKnownSynchronizationKind.SerializableSourceText: - SerializeSourceText((SerializableSourceText)value, writer, context, cancellationToken); - return; - - case WellKnownSynchronizationKind.SourceText: - SerializeSourceText(new SerializableSourceText((SourceText)value), writer, context, cancellationToken); + SerializeSourceText((SerializableSourceText)value, writer, cancellationToken); return; case WellKnownSynchronizationKind.SolutionCompilationState: @@ -164,12 +162,8 @@ public void Serialize(object value, ObjectWriter writer, SolutionReplicationCont ((ProjectStateChecksums)value).Serialize(writer); return; - case WellKnownSynchronizationKind.DocumentState: - ((DocumentStateChecksums)value).Serialize(writer); - return; - - case WellKnownSynchronizationKind.ChecksumCollection: - ((ChecksumCollection)value).WriteTo(writer); + case WellKnownSynchronizationKind.SourceGeneratorExecutionVersionMap: + ((SourceGeneratorExecutionVersionMap)value).WriteTo(writer); return; default: @@ -191,8 +185,6 @@ public object Deserialize(WellKnownSynchronizationKind kind, ObjectReader reader WellKnownSynchronizationKind.SolutionCompilationState => SolutionCompilationStateChecksums.Deserialize(reader), WellKnownSynchronizationKind.SolutionState => SolutionStateChecksums.Deserialize(reader), WellKnownSynchronizationKind.ProjectState => ProjectStateChecksums.Deserialize(reader), - WellKnownSynchronizationKind.DocumentState => DocumentStateChecksums.Deserialize(reader), - WellKnownSynchronizationKind.ChecksumCollection => ChecksumCollection.ReadFrom(reader), WellKnownSynchronizationKind.SolutionAttributes => SolutionInfo.SolutionAttributes.ReadFrom(reader), WellKnownSynchronizationKind.ProjectAttributes => ProjectInfo.ProjectAttributes.ReadFrom(reader), WellKnownSynchronizationKind.DocumentAttributes => DocumentInfo.DocumentAttributes.ReadFrom(reader), @@ -203,7 +195,7 @@ public object Deserialize(WellKnownSynchronizationKind kind, ObjectReader reader WellKnownSynchronizationKind.MetadataReference => DeserializeMetadataReference(reader, cancellationToken), WellKnownSynchronizationKind.AnalyzerReference => DeserializeAnalyzerReference(reader, cancellationToken), WellKnownSynchronizationKind.SerializableSourceText => SerializableSourceText.Deserialize(reader, _storageService, _textService, cancellationToken), - WellKnownSynchronizationKind.SourceText => DeserializeSourceText(reader, cancellationToken), + WellKnownSynchronizationKind.SourceGeneratorExecutionVersionMap => SourceGeneratorExecutionVersionMap.Deserialize(reader), _ => throw ExceptionUtilities.UnexpectedValue(kind), }; } @@ -213,7 +205,7 @@ private IOptionsSerializationService GetOptionsSerializationService(string langu => _lazyLanguageSerializationService.GetOrAdd(languageName, n => _workspaceServices.GetLanguageServices(n).GetRequiredService()); public Checksum CreateParseOptionsChecksum(ParseOptions value) - => Checksum.Create(value, this); + => Checksum.Create((value, @this: this), static (tuple, writer) => tuple.@this.SerializeParseOptions(tuple.value, writer)); } // TODO: convert this to sub class rather than using enum with if statement. diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs index 9a59c289d4d95..d7bbeba322659 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs @@ -17,15 +17,9 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal partial class SerializerService { - private static void SerializeSourceText(SerializableSourceText text, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + private static void SerializeSourceText(SerializableSourceText text, ObjectWriter writer, CancellationToken cancellationToken) { - text.Serialize(writer, context, cancellationToken); - } - - private SourceText DeserializeSourceText(ObjectReader reader, CancellationToken cancellationToken) - { - var serializableSourceText = SerializableSourceText.Deserialize(reader, _storageService, _textService, cancellationToken); - return serializableSourceText.GetText(cancellationToken); + text.Serialize(writer, cancellationToken); } private void SerializeCompilationOptions(CompilationOptions options, ObjectWriter writer, CancellationToken cancellationToken) @@ -92,10 +86,10 @@ private static ProjectReference DeserializeProjectReference(ObjectReader reader, return new ProjectReference(projectId, aliases.ToImmutableArrayOrEmpty(), embedInteropTypes); } - private void SerializeMetadataReference(MetadataReference reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + private void SerializeMetadataReference(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - WriteMetadataReferenceTo(reference, writer, context, cancellationToken); + WriteMetadataReferenceTo(reference, writer, cancellationToken); } private MetadataReference DeserializeMetadataReference(ObjectReader reader, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 9e2b09f3ee6cc..321876a3d0626 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -7,10 +7,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection.Metadata; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; @@ -19,12 +16,12 @@ namespace Microsoft.CodeAnalysis.Serialization; +using static TemporaryStorageService; + internal partial class SerializerService { private const int MetadataFailed = int.MaxValue; - private static readonly ConditionalWeakTable s_lifetimeMap = new(); - public static Checksum CreateChecksum(MetadataReference reference, CancellationToken cancellationToken) { if (reference is PortableExecutableReference portable) @@ -44,7 +41,7 @@ public static Checksum CreateChecksum(AnalyzerReference reference, CancellationT using var stream = SerializableBytes.CreateWritableStream(); - using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) + using (var writer = new ObjectWriter(stream, leaveOpen: true)) { switch (reference) { @@ -62,16 +59,15 @@ public static Checksum CreateChecksum(AnalyzerReference reference, CancellationT return Checksum.Create(stream); } - public virtual void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public virtual void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { if (reference is PortableExecutableReference portable) { - if (portable is ISupportTemporaryStorage supportTemporaryStorage) + if (portable is ISupportTemporaryStorage { StorageHandles: { Count: > 0 } handles } && + TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( + portable, handles, writer, cancellationToken)) { - if (TryWritePortableExecutableReferenceBackedByTemporaryStorageTo(supportTemporaryStorage, writer, context, cancellationToken)) - { - return; - } + return; } WritePortableExecutableReferenceTo(portable, writer, cancellationToken); @@ -143,7 +139,7 @@ private static Checksum CreatePortableExecutableReferenceChecksum(PortableExecut { using var stream = SerializableBytes.CreateWritableStream(); - using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) + using (var writer = new ObjectWriter(stream, leaveOpen: true)) { WritePortableExecutableReferencePropertiesTo(reference, writer, cancellationToken); WriteMvidsTo(TryGetMetadata(reference), writer, cancellationToken); @@ -235,8 +231,7 @@ private PortableExecutableReference ReadPortableExecutableReferenceFrom(ObjectRe var filePath = reader.ReadString(); - var tuple = TryReadMetadataFrom(reader, kind, cancellationToken); - if (tuple == null) + if (TryReadMetadataFrom(reader, kind, cancellationToken) is not (var metadata, var storageHandles)) { // TODO: deal with xml document provider properly // should we shadow copy xml doc comment? @@ -256,7 +251,7 @@ private PortableExecutableReference ReadPortableExecutableReferenceFrom(ObjectRe _documentationService.GetDocumentationProvider(filePath) : XmlDocumentationProvider.Default; return new SerializedMetadataReference( - properties, filePath, tuple.Value.metadata, tuple.Value.storages, documentProvider); + properties, filePath, metadata, storageHandles, documentProvider); } private static void WriteTo(MetadataReferenceProperties properties, ObjectWriter writer, CancellationToken cancellationToken) @@ -313,46 +308,28 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio } private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( - ISupportTemporaryStorage reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + PortableExecutableReference reference, + IReadOnlyList handles, + ObjectWriter writer, + CancellationToken cancellationToken) { - var storages = reference.GetStorages(); - if (storages == null) - { - return false; - } - - // Not clear if name should be allowed to be null here (https://github.com/dotnet/roslyn/issues/43037) - using var pooled = Creator.CreateList<(string? name, long offset, long size)>(); - - foreach (var storage in storages) - { - if (storage is not ITemporaryStorageWithName storage2) - { - return false; - } + Contract.ThrowIfTrue(handles.Count == 0); - context.AddResource(storage); - - pooled.Object.Add((storage2.Name, storage2.Offset, storage2.Size)); - } - - WritePortableExecutableReferenceHeaderTo((PortableExecutableReference)reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); + WritePortableExecutableReferenceHeaderTo(reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); writer.WriteInt32((int)MetadataImageKind.Assembly); - writer.WriteInt32(pooled.Object.Count); + writer.WriteInt32(handles.Count); - foreach (var (name, offset, size) in pooled.Object) + foreach (var handle in handles) { writer.WriteInt32((int)MetadataImageKind.Module); - writer.WriteString(name); - writer.WriteInt64(offset); - writer.WriteInt64(size); + handle.Identifier.WriteTo(writer); } return true; } - private (Metadata metadata, ImmutableArray storages)? TryReadMetadataFrom( + private (Metadata metadata, ImmutableArray storageHandles)? TryReadMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { var imageKind = reader.ReadInt32(); @@ -363,158 +340,87 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT } var metadataKind = (MetadataImageKind)imageKind; - if (_storageService == null) - { - if (metadataKind == MetadataImageKind.Assembly) - { - using var pooledMetadata = Creator.CreateList(); - - var count = reader.ReadInt32(); - for (var i = 0; i < count; i++) - { - metadataKind = (MetadataImageKind)reader.ReadInt32(); - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - -#pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - pooledMetadata.Object.Add(ReadModuleMetadataFrom(reader, kind)); -#pragma warning restore CA2016 - } - - return (AssemblyMetadata.Create(pooledMetadata.Object), storages: default); - } - - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); -#pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storages: default); -#pragma warning restore CA2016 - } - if (metadataKind == MetadataImageKind.Assembly) { - using var pooledMetadata = Creator.CreateList(); - using var pooledStorage = Creator.CreateList(); - var count = reader.ReadInt32(); + + var allMetadata = new FixedSizeArrayBuilder(count); + var allHandles = new FixedSizeArrayBuilder(count); + for (var i = 0; i < count; i++) { metadataKind = (MetadataImageKind)reader.ReadInt32(); Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - var (metadata, storage) = ReadModuleMetadataFrom(reader, kind, cancellationToken); + var (metadata, storageHandle) = ReadModuleMetadataFrom(reader, kind, cancellationToken); - pooledMetadata.Object.Add(metadata); - pooledStorage.Object.Add(storage); + allMetadata.Add(metadata); + allHandles.Add(storageHandle); } - return (AssemblyMetadata.Create(pooledMetadata.Object), pooledStorage.Object.ToImmutableArrayOrEmpty()); + return (AssemblyMetadata.Create(allMetadata.MoveToImmutable()), allHandles.MoveToImmutable()); } + else + { + Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - - var moduleInfo = ReadModuleMetadataFrom(reader, kind, cancellationToken); - return (moduleInfo.metadata, ImmutableArray.Create(moduleInfo.storage)); + var moduleInfo = ReadModuleMetadataFrom(reader, kind, cancellationToken); + return (moduleInfo.metadata, [moduleInfo.storageHandle]); + } } - private (ModuleMetadata metadata, ITemporaryStreamStorageInternal storage) ReadModuleMetadataFrom( + private (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var (storage, length) = GetTemporaryStorage(reader, kind, cancellationToken); - - var storageStream = storage.ReadStream(cancellationToken); - Contract.ThrowIfFalse(length == storageStream.Length); - - GetMetadata(storageStream, length, out var metadata, out var lifeTimeObject); - - // make sure we keep storageStream alive while Metadata is alive - // we use conditional weak table since we can't control metadata liftetime - if (lifeTimeObject != null) - s_lifetimeMap.Add(metadata, lifeTimeObject); - - return (metadata, storage); - } - - private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) - { - Contract.ThrowIfFalse(SerializationKinds.Bits == kind); - - var array = reader.ReadByteArray(); - var pinnedObject = new PinnedObject(array); - - var metadata = ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), array.Length); + Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); - // make sure we keep storageStream alive while Metadata is alive - // we use conditional weak table since we can't control metadata liftetime - s_lifetimeMap.Add(metadata, pinnedObject); + return kind == SerializationKinds.Bits + ? ReadModuleMetadataFromBits() + : ReadModuleMetadataFromMemoryMappedFile(); - return metadata; - } - - private (ITemporaryStreamStorageInternal storage, long length) GetTemporaryStorage( - ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); + (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFromMemoryMappedFile() + { + // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it + // will not be released by the host. + var storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); + var storageHandle = TemporaryStorageService.GetStreamHandle(storageIdentifier); + return ReadModuleMetadataFromStorage(storageHandle); + } - if (kind == SerializationKinds.Bits) + (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFromBits() { - var storage = _storageService.CreateTemporaryStreamStorage(); + // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the + // server side so that we can refer to this data uniformly. using var stream = SerializableBytes.CreateWritableStream(); - CopyByteArrayToStream(reader, stream, cancellationToken); var length = stream.Length; - - stream.Position = 0; - storage.WriteStream(stream, cancellationToken); - - return (storage, length); + var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); + Contract.ThrowIfTrue(length != storageHandle.Identifier.Size); + return ReadModuleMetadataFromStorage(storageHandle); } - else - { - var service2 = (ITemporaryStorageService2)_storageService; - var name = reader.ReadRequiredString(); - var offset = reader.ReadInt64(); - var size = reader.ReadInt64(); - - var storage = service2.AttachTemporaryStreamStorage(name, offset, size); - var length = size; - - return (storage, length); - } - } - - private static void GetMetadata(Stream stream, long length, out ModuleMetadata metadata, out object? lifeTimeObject) - { - if (stream is UnmanagedMemoryStream unmanagedStream) + (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFromStorage( + TemporaryStorageStreamHandle storageHandle) { - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. + // Now read in the module data using that identifier. This will either be reading from the host's memory if + // they passed us the information about that memory segment. Or it will be reading from our own memory if they + // sent us the full contents. + var unmanagedStream = storageHandle.ReadFromTemporaryStorage(cancellationToken); + Contract.ThrowIfFalse(storageHandle.Identifier.Size == unmanagedStream.Length); + + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. unsafe { - metadata = ModuleMetadata.CreateFromMetadata( + var metadata = ModuleMetadata.CreateFromMetadata( (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - lifeTimeObject = null; - return; + return (metadata, storageHandle); } } - - PinnedObject pinnedObject; - if (stream is MemoryStream memory && - memory.TryGetBuffer(out var buffer) && - buffer.Offset == 0) - { - pinnedObject = new PinnedObject(buffer.Array!); - } - else - { - var array = new byte[length]; - stream.Read(array, 0, (int)length); - pinnedObject = new PinnedObject(array); - } - - metadata = ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), (int)length); - lifeTimeObject = pinnedObject; } private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) @@ -561,35 +467,6 @@ private static void WriteUnresolvedAnalyzerReferenceTo(AnalyzerReference referen } } - private sealed class PinnedObject : IDisposable - { - // shouldn't be read-only since GCHandle is a mutable struct - private GCHandle _gcHandle; - - public PinnedObject(byte[] array) - => _gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); - - internal IntPtr GetPointer() - => _gcHandle.AddrOfPinnedObject(); - - private void OnDispose() - { - if (_gcHandle.IsAllocated) - { - _gcHandle.Free(); - } - } - - ~PinnedObject() - => OnDispose(); - - public void Dispose() - { - GC.SuppressFinalize(this); - OnDispose(); - } - } - private sealed class MissingMetadataReference : PortableExecutableReference { private readonly DocumentationProvider _provider; @@ -627,16 +504,22 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private sealed class SerializedMetadataReference : PortableExecutableReference, ISupportTemporaryStorage { private readonly Metadata _metadata; - private readonly ImmutableArray _storagesOpt; + private readonly ImmutableArray _storageHandles; private readonly DocumentationProvider _provider; + public IReadOnlyList StorageHandles => _storageHandles; + public SerializedMetadataReference( - MetadataReferenceProperties properties, string? fullPath, - Metadata metadata, ImmutableArray storagesOpt, DocumentationProvider initialDocumentation) + MetadataReferenceProperties properties, + string? fullPath, + Metadata metadata, + ImmutableArray storageHandles, + DocumentationProvider initialDocumentation) : base(properties, fullPath, initialDocumentation) { + Contract.ThrowIfTrue(storageHandles.IsDefault); _metadata = metadata; - _storagesOpt = storagesOpt; + _storageHandles = storageHandles; _provider = initialDocumentation; } @@ -651,9 +534,6 @@ protected override Metadata GetMetadataImpl() => _metadata; protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties) - => new SerializedMetadataReference(properties, FilePath, _metadata, _storagesOpt, _provider); - - public IReadOnlyList? GetStorages() - => _storagesOpt.IsDefault ? null : _storagesOpt; + => new SerializedMetadataReference(properties, FilePath, _metadata, _storageHandles, _provider); } } diff --git a/src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs b/src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs deleted file mode 100644 index e1cb61be7218f..0000000000000 --- a/src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Serialization; - -internal readonly struct SolutionReplicationContext : IDisposable -{ - private static readonly ObjectPool> s_pool = new(() => []); - - private readonly ConcurrentSet _resources; - - public SolutionReplicationContext() - => _resources = s_pool.Allocate(); - - public void AddResource(IDisposable resource) - => _resources.Add(resource); - - public void Dispose() - { - // TODO: https://github.com/dotnet/roslyn/issues/49973 - // Currently we don't dispose resources, only keep them alive. - // Shouldn't we dispose them? - // _resources.All(resource => resource.Dispose()); - s_pool.ClearAndFree(_resources); - } -} diff --git a/src/Workspaces/Core/Portable/Shared/Collections/IntervalTree.cs b/src/Workspaces/Core/Portable/Shared/Collections/IntervalTree.cs index 0d8a268537a47..a2de671e3cfd8 100644 --- a/src/Workspaces/Core/Portable/Shared/Collections/IntervalTree.cs +++ b/src/Workspaces/Core/Portable/Shared/Collections/IntervalTree.cs @@ -20,6 +20,6 @@ public static IntervalTree Create(in TIntrospector introspe public static IntervalTree Create(in TIntrospector introspector, IEnumerable values = null) where TIntrospector : struct, IIntervalIntrospector { - return IntervalTree.Create(in introspector, values ?? SpecializedCollections.EmptyEnumerable()); + return IntervalTree.Create(in introspector, values ?? []); } } diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/IAsyncEnumerableExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/IAsyncEnumerableExtensions.cs index aadde0b3d6c01..3a390a2769457 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/IAsyncEnumerableExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/IAsyncEnumerableExtensions.cs @@ -32,7 +32,7 @@ public static async Task> ToImmutableArrayAsync(this IAsync await foreach (var value in values.WithCancellation(cancellationToken).ConfigureAwait(false)) result.Add(value); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } /// diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/IFindReferencesResultExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/IFindReferencesResultExtensions.cs index 2a464200b74a7..af8456252d02c 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/IFindReferencesResultExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/IFindReferencesResultExtensions.cs @@ -20,7 +20,7 @@ public static IEnumerable GetDefinitionLocationsToShow( this ISymbol definition) { return definition.IsKind(SymbolKind.Namespace) - ? SpecializedCollections.SingletonEnumerable(definition.Locations.First()) + ? [definition.Locations.First()] : definition.Locations; } @@ -46,6 +46,11 @@ public static bool ShouldShow( public static bool ShouldShowWithNoReferenceLocations( this ISymbol definition, FindReferencesSearchOptions options, bool showMetadataSymbolsWithoutReferences) { + if (options.DisplayAllDefinitions) + { + return true; + } + // If the definition is implicit and we have no references, then we don't want to // clutter the UI with it. if (definition.IsImplicitlyDeclared) @@ -134,6 +139,6 @@ private static ImmutableArray FilterNonMatchingMethodNamesWork result.Add(reference); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageMetadataExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageMetadataExtensions.cs index 5974d620e7f18..fbc0e1b339f68 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageMetadataExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageMetadataExtensions.cs @@ -38,7 +38,7 @@ public static ImmutableDictionary new KeyValuePair>>(kvp.Key, kvp.Value.ToImmutableAndFree())).ToImmutableDictionary(); + return builder.Select(kvp => KeyValuePairUtil.Create(kvp.Key, kvp.Value.ToImmutableAndFree())).ToImmutableDictionary(); } public static ImmutableDictionary>> ToPerLanguageMapWithMultipleLanguages(this IEnumerable> services) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageServiceProviderExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageServiceProviderExtensions.cs index 4c237281e18ea..855a5ac6f7c24 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageServiceProviderExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageServiceProviderExtensions.cs @@ -19,9 +19,7 @@ public static IEnumerable> SelectMatchingExtensions>(); - } + return []; return items.Where(lazy => lazy.Metadata.Language == serviceProvider.Language); } diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs index 2b2102c9603a5..77f2d60898931 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Extensions; @@ -57,22 +58,21 @@ public static IEnumerable GetAllNamespacesAndTypes( this INamespaceSymbol namespaceSymbol, CancellationToken cancellationToken) { - var stack = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(namespaceSymbol); - while (stack.Count > 0) + while (stack.TryPop(out var current)) { cancellationToken.ThrowIfCancellationRequested(); - var current = stack.Pop(); if (current is INamespaceSymbol childNamespace) { - stack.Push(childNamespace.GetMembers().AsEnumerable()); + stack.AddRange(childNamespace.GetMembers()); yield return childNamespace; } else { var child = (INamedTypeSymbol)current; - stack.Push(child.GetTypeMembers()); + stack.AddRange(child.GetTypeMembers()); yield return child; } } @@ -82,18 +82,14 @@ public static IEnumerable GetAllNamespaces( this INamespaceSymbol namespaceSymbol, CancellationToken cancellationToken) { - var stack = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(namespaceSymbol); - while (stack.Count > 0) + while (stack.TryPop(out var childNamespace)) { cancellationToken.ThrowIfCancellationRequested(); - var current = stack.Pop(); - if (current is INamespaceSymbol childNamespace) - { - stack.Push(childNamespace.GetNamespaceMembers()); - yield return childNamespace; - } + stack.AddRange(childNamespace.GetNamespaceMembers()); + yield return childNamespace; } } @@ -114,21 +110,17 @@ public static IEnumerable FindNamespaces( { cancellationToken.ThrowIfCancellationRequested(); - var stack = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(namespaceSymbol); - while (stack.Count > 0) + while (stack.TryPop(out var current)) { cancellationToken.ThrowIfCancellationRequested(); - var current = stack.Pop(); - var matchingChildren = current.GetMembers(namespaceName).OfType(); foreach (var child in matchingChildren) - { yield return child; - } - stack.Push(current.GetNamespaceMembers()); + stack.AddRange(current.GetNamespaceMembers()); } } diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/IParameterSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/IParameterSymbolExtensions.cs index cdadec5afb4a4..e58fdec3a2f85 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/IParameterSymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/IParameterSymbolExtensions.cs @@ -45,10 +45,10 @@ public static IParameterSymbol WithAttributes(this IParameterSymbol parameter, I public static ImmutableArray RenameParameters(this IList parameters, ImmutableArray parameterNames) { - using var _ = ArrayBuilder.GetInstance(parameters.Count, out var result); + var result = new FixedSizeArrayBuilder(parameters.Count); for (var i = 0; i < parameterNames.Length; i++) result.Add(parameters[i].RenameParameter(parameterNames[i])); - return result.ToImmutableAndClear(); + return result.MoveToImmutable(); } } diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs index bf841432cddf8..aba239ec1cb92 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs @@ -344,7 +344,7 @@ private static XNode[] RewriteMany(ISymbol symbol, HashSet? visitedSymb result.AddRange(RewriteInheritdocElements(symbol, visitedSymbols, compilation, child, cancellationToken)); } - return result.ToArray(); + return [.. result]; } private static XNode[]? RewriteInheritdocElement(ISymbol memberSymbol, HashSet? visitedSymbols, Compilation compilation, XElement element, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs index b45c69cfd0dc4..f929abc39b74d 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs @@ -47,7 +47,7 @@ public override ITypeSymbol VisitNamedType(INamedTypeSymbol symbol) if (arguments.SequenceEqual(symbol.TypeArguments)) return symbol; - return symbol.ConstructedFrom.Construct(arguments.ToArray()); + return symbol.ConstructedFrom.Construct([.. arguments]); } public override ITypeSymbol VisitPointerType(IPointerTypeSymbol symbol) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs index 24f728ff9b703..b591d1c9d2818 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs @@ -45,7 +45,7 @@ public override ITypeSymbol VisitNamedType(INamedTypeSymbol symbol) return symbol; } - return symbol.ConstructedFrom.Construct(arguments.ToArray()); + return symbol.ConstructedFrom.Construct([.. arguments]); } public override ITypeSymbol VisitPointerType(IPointerTypeSymbol symbol) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs index 31510f8004fae..93bde82d583ef 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs @@ -49,7 +49,7 @@ public override ITypeSymbol VisitNamedType(INamedTypeSymbol symbol) return symbol; } - return symbol.ConstructedFrom.Construct(arguments.ToArray()); + return symbol.ConstructedFrom.Construct([.. arguments]); } public override ITypeSymbol VisitPointerType(IPointerTypeSymbol symbol) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs index 3dfc48994b3a6..4153fcdbc4fa3 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs @@ -27,7 +27,7 @@ internal static partial class ITypeSymbolExtensions /// interfaceMember, or this type doesn't supply a member that successfully implements /// interfaceMember). /// - public static async Task> FindImplementationsForInterfaceMemberAsync( + public static ImmutableArray FindImplementationsForInterfaceMember( this ITypeSymbol typeSymbol, ISymbol interfaceMember, Solution solution, @@ -97,13 +97,11 @@ not SymbolKind.Method and // OriginalSymbolMatch allows types to be matched across different assemblies if they are considered to // be the same type, which provides a more accurate implementations list for interfaces. var constructedInterfaceMember = - await constructedInterface.GetMembers(interfaceMember.Name).FirstOrDefaultAsync( - typeSymbol => SymbolFinder.OriginalSymbolsMatchAsync(solution, typeSymbol, interfaceMember, cancellationToken)).ConfigureAwait(false); + constructedInterface.GetMembers(interfaceMember.Name).FirstOrDefault( + typeSymbol => SymbolFinder.OriginalSymbolsMatch(solution, typeSymbol, interfaceMember)); if (constructedInterfaceMember == null) - { continue; - } // Now we need to walk the base type chain, but we start at the first type that actually // has the interface directly in its interface hierarchy. @@ -126,7 +124,7 @@ await constructedInterface.GetMembers(interfaceMember.Name).FirstOrDefaultAsync( } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } public static ISymbol? FindImplementations(this ITypeSymbol typeSymbol, ISymbol constructedInterfaceMember, SolutionServices services) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions.cs index 5ee5302342f19..ecce1282f978b 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions.cs @@ -91,7 +91,7 @@ private static bool ShouldGenerateThisConstructorCall( .OfType() .Where(field => !field.IsStatic) .Select(field => field.AssociatedSymbol ?? field) - .Except(parameterToExistingFieldMap?.Values ?? SpecializedCollections.EmptyEnumerable()) + .Except(parameterToExistingFieldMap?.Values ?? []) .Any(); } @@ -117,7 +117,7 @@ public static ImmutableArray CreateFieldsForParameters( } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public static ImmutableArray CreatePropertiesForParameters( @@ -147,7 +147,7 @@ public static ImmutableArray CreatePropertiesForParameters( } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static bool TryGetValue(IDictionary? dictionary, string key, [NotNullWhen(true)] out string? value) @@ -191,9 +191,7 @@ public static SyntaxNode CreateNullCheckAndThrowStatement( var throwStatement = factory.CreateThrowArgumentNullExceptionStatement(semanticModel.Compilation, parameter); // generates: if (s is null) { throw new ArgumentNullException(nameof(s)); } - return factory.IfStatement( - condition, - SpecializedCollections.SingletonEnumerable(throwStatement)); + return factory.IfStatement(condition, [throwStatement]); } public static SyntaxNode CreateThrowArgumentNullExceptionStatement(this SyntaxGenerator factory, Compilation compilation, IParameterSymbol parameter) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs index bb3d0623e47c8..6ebbbe5063f0c 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs @@ -107,17 +107,17 @@ private static ImmutableArray CreateEqualsMethodStatements( ImmutableArray members, string localNameOpt) { - using var _1 = ArrayBuilder.GetInstance(out var statements); // A ref like type can not be boxed. Because of this an overloaded Equals taking object in the general case // can never be true, because an equivalent object can never be boxed into the object itself. Therefore only // need to return false. if (containingType.IsRefLikeType) { - statements.Add(factory.ReturnStatement(factory.FalseLiteralExpression())); - return statements.ToImmutable(); + return [factory.ReturnStatement(factory.FalseLiteralExpression())]; } + using var statements = TemporaryArray.Empty; + // Come up with a good name for the local variable we're going to compare against. // For example, if the class name is "CustomerOrder" then we'll generate: // @@ -203,7 +203,7 @@ private static ImmutableArray CreateEqualsMethodStatements( statements.Add(factory.ReturnStatement( expressions.Aggregate(factory.LogicalAndExpression))); - return statements.ToImmutable(); + return statements.ToImmutableAndClear(); } private static void AddMemberChecks( diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs index f9de85f7493c1..174d252614bbc 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs @@ -235,7 +235,7 @@ public ImmutableArray ActiveDiagnosticTokens return []; } - return _diagnosticTokenList.ToImmutableArray(); + return [.. _diagnosticTokenList]; } } } diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs index 2fb062340a4c3..ed5be74296ee3 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs @@ -151,7 +151,7 @@ public async Task WaitAllAsync(Workspace? workspace, string[]? featureNames = nu do { // wait for all current tasks to be done for the time given - if (Task.WaitAll(tasks.ToArray(), smallTimeout)) + if (Task.WaitAll([.. tasks], smallTimeout)) { // current set of tasks are done. // see whether there are new tasks added while we were waiting diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs index e145e03e49b25..4e7cd40576ebd 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs @@ -28,6 +28,7 @@ internal static class FeatureAttribute public const string EventHookup = nameof(EventHookup); public const string ExtractMethod = nameof(ExtractMethod); public const string FindReferences = nameof(FindReferences); + public const string SemanticSearch = nameof(SemanticSearch); public const string GlobalOperation = nameof(GlobalOperation); public const string GoToBase = nameof(GoToBase); public const string GoToDefinition = nameof(GoToDefinition); @@ -47,6 +48,7 @@ internal static class FeatureAttribute public const string NavigableSymbols = nameof(NavigableSymbols); public const string NavigateTo = nameof(NavigateTo); public const string NavigationBar = nameof(NavigationBar); + public const string OnTheFlyDocs = nameof(OnTheFlyDocs); public const string Outlining = nameof(Outlining); public const string OrganizeDocument = nameof(OrganizeDocument); public const string PackageInstaller = nameof(PackageInstaller); diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs index f0cab2c4b4f33..858e3f85d4ed3 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs @@ -98,6 +98,8 @@ internal class AsyncBatchingWorkQueue #endregion + /// Callback to process queued work items. The list of items passed in is + /// guaranteed to always be non-empty. public AsyncBatchingWorkQueue( TimeSpan delay, Func, CancellationToken, ValueTask> processBatchAsync, diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs b/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs index 6cc6cad8d4b1a..4d740090dc8a4 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; namespace Microsoft.CodeAnalysis.Shared.Utilities; @@ -113,7 +114,7 @@ private static int ComputeK(int expectedCount, double falsePositiveProbability) /// /// Murmur hash is public domain. Actual code is included below as reference. /// - private int ComputeHash(string key, int seed) + private static int ComputeHash(string key, int seed, bool isCaseSensitive) { unchecked { @@ -127,8 +128,8 @@ private int ComputeHash(string key, int seed) var index = 0; while (numberOfCharsLeft >= 2) { - var c1 = GetCharacter(key, index); - var c2 = GetCharacter(key, index + 1); + var c1 = GetCharacter(key, index, isCaseSensitive); + var c2 = GetCharacter(key, index + 1, isCaseSensitive); h = CombineTwoCharacters(h, c1, c2); @@ -140,7 +141,7 @@ private int ComputeHash(string key, int seed) // odd length. if (numberOfCharsLeft == 1) { - var c = GetCharacter(key, index); + var c = GetCharacter(key, index, isCaseSensitive); h = CombineLastCharacter(h, c); } @@ -225,10 +226,10 @@ private static uint CombineTwoCharacters(uint h, uint c1, uint c2) } } - private char GetCharacter(string key, int index) + private static char GetCharacter(string key, int index, bool isCaseSensitive) { var c = key[index]; - return _isCaseSensitive ? c : char.ToLowerInvariant(c); + return isCaseSensitive ? c : char.ToLowerInvariant(c); } private static char GetCharacter(long key, int index) @@ -319,13 +320,13 @@ public void Add(string value) { for (var i = 0; i < _hashFunctionCount; i++) { - _bitArray[GetBitArrayIndex(value, i)] = true; + var hash = ComputeHash(value, i, _isCaseSensitive); + _bitArray[GetBitArrayIndexFromHash(hash)] = true; } } - private int GetBitArrayIndex(string value, int i) + private int GetBitArrayIndexFromHash(int hash) { - var hash = ComputeHash(value, i); hash %= _bitArray.Length; return Math.Abs(hash); } @@ -334,22 +335,24 @@ public void Add(long value) { for (var i = 0; i < _hashFunctionCount; i++) { - _bitArray[GetBitArrayIndex(value, i)] = true; + var hash = ComputeHash(value, i); + _bitArray[GetBitArrayIndexFromHash(hash)] = true; } } - private int GetBitArrayIndex(long value, int i) - { - var hash = ComputeHash(value, i); - hash %= _bitArray.Length; - return Math.Abs(hash); - } - public bool ProbablyContains(string value) { + // Request an array of immutable hashes for this input. Note that it's possible + // that the returned array might return a cached entry calculated by a different + // bloom filter and thus might have more entries than we need, but it's ok as + // it's guaranteed that the first _hashFunctionCount of those values are the values + // we would have computed had we not used the cache. + var hashes = BloomFilterHash.GetOrCreateHashArray(value, _isCaseSensitive, _hashFunctionCount); + for (var i = 0; i < _hashFunctionCount; i++) { - if (!_bitArray[GetBitArrayIndex(value, i)]) + var hash = hashes[i]; + if (!_bitArray[GetBitArrayIndexFromHash(hash)]) { return false; } @@ -362,7 +365,8 @@ public bool ProbablyContains(long value) { for (var i = 0; i < _hashFunctionCount; i++) { - if (!_bitArray[GetBitArrayIndex(value, i)]) + var hash = ComputeHash(value, i); + if (!_bitArray[GetBitArrayIndexFromHash(hash)]) { return false; } @@ -395,4 +399,86 @@ private static bool IsEquivalent(BitArray array1, BitArray array2) return true; } + + /// + /// Provides mechanism to efficiently obtain bloom filter hash for a value. Backed by a single element cache. + /// + internal sealed class BloomFilterHash + { + private static BloomFilterHash? s_cachedHash; + + private readonly string _value; + private readonly bool _isCaseSensitive; + private readonly ImmutableArray _hashes; + + private BloomFilterHash(string value, bool isCaseSensitive, int hashFunctionCount) + { + _value = value; + _isCaseSensitive = isCaseSensitive; + + var hashBuilder = new FixedSizeArrayBuilder(hashFunctionCount); + + for (var i = 0; i < hashFunctionCount; i++) + hashBuilder.Add(BloomFilter.ComputeHash(value, i, _isCaseSensitive)); + + _hashes = hashBuilder.MoveToImmutable(); + } + + /// + /// Although calculating this hash isn't terribly expensive, it does involve multiple + /// (usually around 13) hashings of the string (the actual count is ). + /// The typical usage pattern of bloom filters is that some operation (eg: find references) + /// requires asking a multitude of bloom filters whether a particular value is likely contained. + /// The vast majority of those bloom filters will end up hashing that string to the same values, so + /// we put those values into a simple cache and see if it can be used before calculating. + /// Local testing has put the hit rate of this at around 99%. + /// + /// Note that it's possible for this method to return an array from the cache longer than hashFunctionCount, + /// but if so, it's guaranteed that the values returned in the first hashFunctionCount entries are + /// the same as if the cache hadn't been used. + /// + public static ImmutableArray GetOrCreateHashArray(string value, bool isCaseSensitive, int hashFunctionCount) + { + var cachedHash = s_cachedHash; + + // Not an equivalency check on the hashFunctionCount as a longer array is ok. This is because the + // values in the array are determined by value and isCaseSensitive and hashFunctionCount is simply + // used to determine the length of the returned array. As long as the cached entry matches the value + // and isCaseSensitive and is at least as long as we need, then we can use it. + if (cachedHash == null + || cachedHash._isCaseSensitive != isCaseSensitive + || cachedHash._hashes.Length < hashFunctionCount + || cachedHash._value != value) + { + cachedHash = new BloomFilterHash(value, isCaseSensitive, hashFunctionCount); + s_cachedHash = cachedHash; + } + + return cachedHash._hashes; + } + + // Used only by tests + internal static bool TryGetCachedEntry(out bool isCaseSensitive, out string value) + { + var cachedHash = s_cachedHash; + + if (cachedHash == null) + { + isCaseSensitive = false; + value = string.Empty; + + return false; + } + + isCaseSensitive = cachedHash._isCaseSensitive; + value = cachedHash._value; + + return true; + } + + internal static void ResetCachedEntry() + { + s_cachedHash = null; + } + } } diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs new file mode 100644 index 0000000000000..2de076fc5cbd0 --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs @@ -0,0 +1,170 @@ +// Licensed to the .NET Foundation under one or more 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.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Shared.Utilities; + +internal readonly record struct ProducerConsumerOptions +{ + /// + /// Used when the consumeItems routine will only pull items on a single thread (never concurrently). produceItems + /// can be called concurrently on many threads. + /// + public static readonly ProducerConsumerOptions SingleReaderOptions = new() { SingleReader = true }; + + /// + /// Used when the consumeItems routine will only pull items on a single thread (never concurrently). produceItems + /// can be called on a single thread as well (never concurrently). + /// + public static readonly ProducerConsumerOptions SingleReaderWriterOptions = new() { SingleReader = true, SingleWriter = true }; + + /// + public bool SingleWriter { get; init; } + + /// + public bool SingleReader { get; init; } +} + +internal static class ProducerConsumer +{ + /// + /// Version of when caller the prefers the results being pre-packaged into arrays to process. + /// + public static Task RunAsync( + ProducerConsumerOptions options, + Func, TArgs, Task> produceItems, + Func, TArgs, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + return RunImplAsync( + options, + static (onItemFound, args) => args.produceItems(onItemFound, args.args), + static (reader, args) => ConsumeItemsAsArrayAsync(reader, args.consumeItems, args.args, args.cancellationToken), + (produceItems, consumeItems, args, cancellationToken), + cancellationToken); + + static async Task ConsumeItemsAsArrayAsync( + ChannelReader reader, + Func, TArgs, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var items); + + while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + // Grab as many items as we can from the channel at once and report in a single array. Then wait for the + // next set of items to be available. + while (reader.TryRead(out var item)) + items.Add(item); + + await consumeItems(items.ToImmutableAndClear(), args).ConfigureAwait(false); + } + } + } + + /// + /// Version of when the caller prefers working with a stream of results. + /// + public static Task RunAsync( + ProducerConsumerOptions options, + Func, TArgs, Task> produceItems, + Func, TArgs, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + return RunImplAsync( + options, + static (onItemFound, args) => args.produceItems(onItemFound, args.args), + static (reader, args) => args.consumeItems(reader.ReadAllAsync(args.cancellationToken), args.args), + (produceItems, consumeItems, args, cancellationToken), + cancellationToken); + } + + /// + /// Helper utility for the pattern of a pair of a production routine and consumption routine using a channel to + /// coordinate data transfer. The provided are used to create a , which will then then manage the rules and behaviors around the routines. Importantly, the + /// channel handles backpressure, ensuring that if the consumption routine cannot keep up, that the production + /// routine will be throttled. + /// + /// is the routine called to actually produce the items. It will be passed an + /// action that can be used to write items to the channel. Note: the channel itself will have rules depending on if + /// that writing can happen concurrently multiple write threads or just a single writer. See for control of this when creating the channel. + /// + /// is the routine called to consume the items. Similarly, reading can have just a + /// single reader or multiple readers, depending on the value passed into . + /// + private static async Task RunImplAsync( + ProducerConsumerOptions options, + Func, TArgs, Task> produceItems, + Func, TArgs, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + var channel = Channel.CreateUnbounded(new() + { + SingleReader = options.SingleReader, + SingleWriter = options.SingleWriter, + }); + + // When cancellation happens, attempt to close the channel. That will unblock the task processing the items. + // Capture-free version is only available on netcore unfortunately. + using var _ = cancellationToken.Register( +#if NET + static (obj, cancellationToken) => ((Channel)obj!).Writer.TryComplete(new OperationCanceledException(cancellationToken)), + state: channel); +#else + () => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); +#endif + + await Task.WhenAll( + ProduceItemsAndWriteToChannelAsync(), + ReadFromChannelAndConsumeItemsAsync()).ConfigureAwait(false); + + return; + + async Task ReadFromChannelAndConsumeItemsAsync() + { + await Task.Yield().ConfigureAwait(false); + await consumeItems(channel.Reader, args).ConfigureAwait(false); + } + + async Task ProduceItemsAndWriteToChannelAsync() + { + Exception? exception = null; + try + { + await Task.Yield().ConfigureAwait(false); + + // It's ok to use TryWrite here. TryWrite always succeeds unless the channel is completed. And the + // channel is only ever completed by us (after produceItems completes or throws an exception) or if the + // cancellationToken is triggered above in RunAsync. In that latter case, it's ok for writing to the + // channel to do nothing as we no longer need to write out those assets to the pipe. + await produceItems(item => channel.Writer.TryWrite(item), args).ConfigureAwait(false); + } + catch (Exception ex) when ((exception = ex) == null) + { + throw ExceptionUtilities.Unreachable(); + } + finally + { + // No matter what path we take (exceptional or non-exceptional), always complete the channel so the + // writing task knows it's done. + channel.Writer.TryComplete(exception); + } + } + } +} diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs new file mode 100644 index 0000000000000..fd0cd177b228e --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.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; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.Shared.Utilities; + +#pragma warning disable CA1068 // CancellationToken parameters must come last + +internal static class RoslynParallel +{ + public static Task ForEachAsync( + IEnumerable source, + CancellationToken cancellationToken, + Func body) + { + return ForEachAsync(source, GetParallelOptions(cancellationToken), body); + } + + private static ParallelOptions GetParallelOptions(CancellationToken cancellationToken) + => new() { TaskScheduler = TaskScheduler.Default, CancellationToken = cancellationToken }; + + public static async Task ForEachAsync( + IEnumerable source, + ParallelOptions parallelOptions, + Func body) + { + var cancellationToken = parallelOptions.CancellationToken; + if (cancellationToken.IsCancellationRequested) + return; + +#if NET + await Parallel.ForEachAsync(source, parallelOptions, body).ConfigureAwait(false); +#else + using var _ = ArrayBuilder.GetInstance(out var tasks); + + foreach (var item in source) + { + tasks.Add(CreateWorkAsync( + parallelOptions.TaskScheduler, + async () => await body(item, cancellationToken).ConfigureAwait(false), + cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); +#endif + } + + public static Task ForEachAsync( + IAsyncEnumerable source, + CancellationToken cancellationToken, + Func body) + { + return ForEachAsync(source, GetParallelOptions(cancellationToken), body); + } + + public static async Task ForEachAsync( + IAsyncEnumerable source, + ParallelOptions parallelOptions, + Func body) + { + var cancellationToken = parallelOptions.CancellationToken; + if (cancellationToken.IsCancellationRequested) + return; + +#if NET + await Parallel.ForEachAsync(source, parallelOptions, body).ConfigureAwait(false); +#else + using var _ = ArrayBuilder.GetInstance(out var tasks); + + await foreach (var item in source) + { + tasks.Add(CreateWorkAsync( + parallelOptions.TaskScheduler, + async () => await body(item, cancellationToken).ConfigureAwait(false), + cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); +#endif + } + + public static Task CreateWorkAsync(TaskScheduler scheduler, Func createWorkAsync, CancellationToken cancellationToken) + => Task.Factory.StartNew(createWorkAsync, cancellationToken, TaskCreationOptions.None, scheduler).Unwrap(); +} diff --git a/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs b/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs index 5e03b565846ba..3a3bf94dfb4a5 100644 --- a/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs +++ b/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs @@ -152,7 +152,7 @@ bool isNodeOrTokenOutsideSimplifySpans(SyntaxNodeOrToken nodeOrToken) computeReplacementNode: (o, n) => TransformReducedNode(reducedNodesMap[o], n), tokens: reducedTokensMap.Keys, computeReplacementToken: (o, n) => reducedTokensMap[o], - trivia: SpecializedCollections.EmptyEnumerable(), + trivia: [], computeReplacementTrivia: null); document = document.WithSyntaxRoot(root); diff --git a/src/Workspaces/Core/Portable/Simplification/Simplifier.cs b/src/Workspaces/Core/Portable/Simplification/Simplifier.cs index b6011d041fb00..be5b44f48f8b2 100644 --- a/src/Workspaces/Core/Portable/Simplification/Simplifier.cs +++ b/src/Workspaces/Core/Portable/Simplification/Simplifier.cs @@ -208,12 +208,12 @@ public static Task ReduceAsync(Document document, TextSpan span, Optio } #pragma warning disable RS0030 // Do not used banned APIs - return ReduceAsync(document, SpecializedCollections.SingletonEnumerable(span), optionSet, cancellationToken); + return ReduceAsync(document, [span], optionSet, cancellationToken); } #pragma warning restore internal static Task ReduceAsync(Document document, TextSpan span, SimplifierOptions options, CancellationToken cancellationToken) - => ReduceAsync(document, SpecializedCollections.SingletonEnumerable(span), options, cancellationToken); + => ReduceAsync(document, [span], options, cancellationToken); /// /// Reduce the sub-trees annotated with found within the specified spans. diff --git a/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs b/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs index 046ffcf07b3e1..659de283e8849 100644 --- a/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs +++ b/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.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; using System.Collections.Immutable; using System.Runtime.Serialization; +using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Text; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.SourceGeneration; @@ -20,7 +21,7 @@ internal interface IRemoteSourceGenerationService /// compare that to the prior generated documents it has to see if it can reuse those directly, or if it needs to /// remove any documents no longer around, add any new documents, or change the contents of any existing documents. /// - ValueTask> GetSourceGenerationInfoAsync( + ValueTask> GetSourceGenerationInfoAsync( Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken); /// @@ -30,6 +31,12 @@ internal interface IRemoteSourceGenerationService /// ValueTask> GetContentsAsync( Checksum solutionChecksum, ProjectId projectId, ImmutableArray documentIds, CancellationToken cancellationToken); + + /// + /// Whether or not the specified analyzer references have source generators or not. + /// + ValueTask HasGeneratorsAsync( + Checksum solutionChecksum, ProjectId projectId, ImmutableArray analyzerReferenceChecksums, string language, CancellationToken cancellationToken); } /// diff --git a/src/Workspaces/Core/Portable/SourceGeneratorTelemetry/ISourceGeneratorTelemetryCollectorWorkspaceService.cs b/src/Workspaces/Core/Portable/SourceGeneratorTelemetry/ISourceGeneratorTelemetryCollectorWorkspaceService.cs index 0d106c168eb18..27422e425657a 100644 --- a/src/Workspaces/Core/Portable/SourceGeneratorTelemetry/ISourceGeneratorTelemetryCollectorWorkspaceService.cs +++ b/src/Workspaces/Core/Portable/SourceGeneratorTelemetry/ISourceGeneratorTelemetryCollectorWorkspaceService.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 Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis.SourceGeneratorTelemetry; internal interface ISourceGeneratorTelemetryCollectorWorkspaceService : IWorkspaceService { - void CollectRunResult(GeneratorDriverRunResult driverRunResult, GeneratorDriverTimingInfo driverTimingInfo, ProjectState project); + void CollectRunResult(GeneratorDriverRunResult driverRunResult, GeneratorDriverTimingInfo driverTimingInfo, Func getAnalyzerReference); } diff --git a/src/Workspaces/Core/Portable/SourceGeneratorTelemetry/SourceGeneratorTelemetryCollectorWorkspaceService.cs b/src/Workspaces/Core/Portable/SourceGeneratorTelemetry/SourceGeneratorTelemetryCollectorWorkspaceService.cs index 4414a2e4cfd44..48fa40db7307f 100644 --- a/src/Workspaces/Core/Portable/SourceGeneratorTelemetry/SourceGeneratorTelemetryCollectorWorkspaceService.cs +++ b/src/Workspaces/Core/Portable/SourceGeneratorTelemetry/SourceGeneratorTelemetryCollectorWorkspaceService.cs @@ -42,19 +42,22 @@ public GeneratorTelemetryKey(ISourceGenerator generator, AnalyzerReference analy private readonly StatisticLogAggregator _elapsedTimeByGenerator = new(); private readonly StatisticLogAggregator _producedFilesByGenerator = new(); - private GeneratorTelemetryKey GetTelemetryKey(ISourceGenerator generator, ProjectState project) - => _generatorTelemetryKeys.GetValue(generator, g => new GeneratorTelemetryKey(g, project.GetAnalyzerReferenceForGenerator(g))); + private GeneratorTelemetryKey GetTelemetryKey(ISourceGenerator generator, Func getAnalyzerReference) + => _generatorTelemetryKeys.GetValue(generator, g => new GeneratorTelemetryKey(g, getAnalyzerReference(g))); - public void CollectRunResult(GeneratorDriverRunResult driverRunResult, GeneratorDriverTimingInfo driverTimingInfo, ProjectState project) + public void CollectRunResult( + GeneratorDriverRunResult driverRunResult, + GeneratorDriverTimingInfo driverTimingInfo, + Func getAnalyzerReference) { foreach (var generatorTime in driverTimingInfo.GeneratorTimes) { - _elapsedTimeByGenerator.AddDataPoint(GetTelemetryKey(generatorTime.Generator, project), generatorTime.ElapsedTime); + _elapsedTimeByGenerator.AddDataPoint(GetTelemetryKey(generatorTime.Generator, getAnalyzerReference), generatorTime.ElapsedTime); } foreach (var generatorResult in driverRunResult.Results) { - _producedFilesByGenerator.AddDataPoint(GetTelemetryKey(generatorResult.Generator, project), generatorResult.GeneratedSources.Length); + _producedFilesByGenerator.AddDataPoint(GetTelemetryKey(generatorResult.Generator, getAnalyzerReference), generatorResult.GeneratedSources.Length); } } diff --git a/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs index 0e6feed57a96e..15a9ef2cd4aa3 100644 --- a/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs @@ -17,19 +17,12 @@ namespace Microsoft.CodeAnalysis.Storage; /// A service that enables storing and retrieving of information associated with solutions, /// projects or documents across runtime sessions. /// -internal abstract partial class AbstractPersistentStorageService : IChecksummedPersistentStorageService +internal abstract partial class AbstractPersistentStorageService(IPersistentStorageConfiguration configuration) : IChecksummedPersistentStorageService { - protected readonly IPersistentStorageConfiguration Configuration; + protected readonly IPersistentStorageConfiguration Configuration = configuration; - /// - /// This lock guards all mutable fields in this type. - /// private readonly SemaphoreSlim _lock = new(initialCount: 1); - private ReferenceCountedDisposable? _currentPersistentStorage; - private SolutionId? _currentPersistentStorageSolutionId; - - protected AbstractPersistentStorageService(IPersistentStorageConfiguration configuration) - => Configuration = configuration; + private IChecksummedPersistentStorage? _currentPersistentStorage; protected abstract string GetDatabaseFilePath(string workingFolderPath); @@ -38,88 +31,65 @@ protected AbstractPersistentStorageService(IPersistentStorageConfiguration confi /// 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, CancellationToken cancellationToken); - protected abstract bool ShouldDeleteDatabase(Exception exception); + protected abstract ValueTask TryOpenDatabaseAsync(SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, IPersistentStorageFaultInjector? faultInjector, CancellationToken cancellationToken); public ValueTask GetStorageAsync(SolutionKey solutionKey, CancellationToken cancellationToken) - { - return solutionKey.FilePath == null - ? new(NoOpPersistentStorage.GetOrThrow(Configuration.ThrowOnFailure)) - : GetStorageWorkerAsync(solutionKey, cancellationToken); - } + => GetStorageAsync(solutionKey, this.Configuration, faultInjector: null, cancellationToken); - internal async ValueTask GetStorageWorkerAsync(SolutionKey solutionKey, CancellationToken cancellationToken) + public async ValueTask GetStorageAsync( + SolutionKey solutionKey, + IPersistentStorageConfiguration configuration, + IPersistentStorageFaultInjector? faultInjector, + CancellationToken cancellationToken) { - using (await _lock.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - // Do we already have storage for this? - if (solutionKey.Id == _currentPersistentStorageSolutionId) - { - // We do, great. Increment our ref count for our caller. They'll decrement it - // when done with it. - return PersistentStorageReferenceCountedDisposableWrapper.AddReferenceCountToAndCreateWrapper(_currentPersistentStorage!); - } - - var workingFolder = Configuration.TryGetStorageLocation(solutionKey); - if (workingFolder == null) - return NoOpPersistentStorage.GetOrThrow(Configuration.ThrowOnFailure); - - // If we already had some previous cached service, let's let it start cleaning up - if (_currentPersistentStorage != null) - { - var storageToDispose = _currentPersistentStorage; - - // Kick off a task to actually go dispose the previous cached storage instance. - // This will remove the single ref count we ourselves added when we cached the - // instance. Then once all other existing clients who are holding onto this - // instance let go, it will finally get truly disposed. - // This operation is not safe to cancel (as dispose must happen). - _ = Task.Run(storageToDispose.Dispose, CancellationToken.None); + if (solutionKey.FilePath == null) + return NoOpPersistentStorage.GetOrThrow(solutionKey, Configuration.ThrowOnFailure); - _currentPersistentStorage = null; - _currentPersistentStorageSolutionId = null; - } + // Without taking the lock, see if we can lookup a storage for this key. + var existing = _currentPersistentStorage; + if (existing?.SolutionKey == solutionKey) + return existing; - var storage = await CreatePersistentStorageAsync(solutionKey, workingFolder, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(storage); + var workingFolder = configuration.TryGetStorageLocation(solutionKey); + if (workingFolder == null) + return NoOpPersistentStorage.GetOrThrow(solutionKey, Configuration.ThrowOnFailure); - // Create and cache a new storage instance associated with this particular solution. - // It will initially have a ref-count of 1 due to our reference to it. - _currentPersistentStorage = new ReferenceCountedDisposable(storage); - _currentPersistentStorageSolutionId = solutionKey.Id; + using (await _lock.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + // Recheck if we have a storage for this key after taking the lock. + if (_currentPersistentStorage?.SolutionKey != solutionKey) + _currentPersistentStorage = await CreatePersistentStorageAsync(solutionKey, workingFolder, faultInjector, cancellationToken).ConfigureAwait(false); - // Now increment the reference count and return to our caller. The current ref - // count for this instance will be 2. Until all the callers *and* us decrement - // the refcounts, this instance will not be actually disposed. - return PersistentStorageReferenceCountedDisposableWrapper.AddReferenceCountToAndCreateWrapper(_currentPersistentStorage); + return _currentPersistentStorage; } } private async ValueTask CreatePersistentStorageAsync( - SolutionKey solutionKey, string workingFolderPath, CancellationToken cancellationToken) + SolutionKey solutionKey, string workingFolderPath, IPersistentStorageFaultInjector? faultInjector, 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, cancellationToken).ConfigureAwait(false) ?? - await TryCreatePersistentStorageAsync(solutionKey, workingFolderPath, cancellationToken).ConfigureAwait(false); + var result = await TryCreatePersistentStorageAsync(solutionKey, workingFolderPath, faultInjector, cancellationToken).ConfigureAwait(false) ?? + await TryCreatePersistentStorageAsync(solutionKey, workingFolderPath, faultInjector, cancellationToken).ConfigureAwait(false); if (result != null) return result; - return NoOpPersistentStorage.GetOrThrow(Configuration.ThrowOnFailure); + return NoOpPersistentStorage.GetOrThrow(solutionKey, Configuration.ThrowOnFailure); } private async ValueTask TryCreatePersistentStorageAsync( SolutionKey solutionKey, string workingFolderPath, + IPersistentStorageFaultInjector? faultInjector, CancellationToken cancellationToken) { var databaseFilePath = GetDatabaseFilePath(workingFolderPath); try { - return await TryOpenDatabaseAsync(solutionKey, workingFolderPath, databaseFilePath, cancellationToken).ConfigureAwait(false); + return await TryOpenDatabaseAsync(solutionKey, workingFolderPath, databaseFilePath, faultInjector, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (Recover(e)) { @@ -131,133 +101,14 @@ bool Recover(Exception ex) StorageDatabaseLogger.LogException(ex); if (Configuration.ThrowOnFailure) - { return false; - } - if (ShouldDeleteDatabase(ex)) - { - // this was not a normal exception that we expected during DB open. - // Report this so we can try to address whatever is causing this. - FatalError.ReportAndCatch(ex); - IOUtilities.PerformIO(() => Directory.Delete(Path.GetDirectoryName(databaseFilePath)!, recursive: true)); - } + // this was not a normal exception that we expected during DB open. + // Report this so we can try to address whatever is causing this. + FatalError.ReportAndCatch(ex); + IOUtilities.PerformIO(() => Directory.Delete(Path.GetDirectoryName(databaseFilePath)!, recursive: true)); return true; } } - - private void Shutdown() - { - ReferenceCountedDisposable? storage = null; - - lock (_lock) - { - // We will transfer ownership in a thread-safe way out so we can dispose outside the lock - storage = _currentPersistentStorage; - _currentPersistentStorage = null; - _currentPersistentStorageSolutionId = null; - } - - // Dispose storage outside of the lock. Note this only removes our reference count; clients who are still - // using this will still be holding a reference count. - storage?.Dispose(); - } - - internal TestAccessor GetTestAccessor() - => new(this); - - internal readonly struct TestAccessor(AbstractPersistentStorageService service) - { - public void Shutdown() - => service.Shutdown(); - } - - /// - /// A trivial wrapper that we can hand out for instances from the - /// that wraps the underlying singleton. - /// - private sealed class PersistentStorageReferenceCountedDisposableWrapper : IChecksummedPersistentStorage - { - private readonly ReferenceCountedDisposable _storage; - - private PersistentStorageReferenceCountedDisposableWrapper(ReferenceCountedDisposable storage) - => _storage = storage; - - public static IChecksummedPersistentStorage AddReferenceCountToAndCreateWrapper(ReferenceCountedDisposable storage) - { - // This should only be called from a caller that has a non-null storage that it - // already has a reference on. So .TryAddReference cannot fail. - return new PersistentStorageReferenceCountedDisposableWrapper(storage.TryAddReference() ?? throw ExceptionUtilities.Unreachable()); - } - - public void Dispose() - => _storage.Dispose(); - - public ValueTask DisposeAsync() - => _storage.DisposeAsync(); - - public Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken) - => _storage.Target.ChecksumMatchesAsync(name, checksum, cancellationToken); - - public Task ChecksumMatchesAsync(Project project, string name, Checksum checksum, CancellationToken cancellationToken) - => _storage.Target.ChecksumMatchesAsync(project, name, checksum, cancellationToken); - - public Task ChecksumMatchesAsync(Document document, string name, Checksum checksum, CancellationToken cancellationToken) - => _storage.Target.ChecksumMatchesAsync(document, name, checksum, cancellationToken); - - public Task ChecksumMatchesAsync(ProjectKey project, string name, Checksum checksum, CancellationToken cancellationToken) - => _storage.Target.ChecksumMatchesAsync(project, name, checksum, cancellationToken); - - public Task ChecksumMatchesAsync(DocumentKey document, string name, Checksum checksum, CancellationToken cancellationToken) - => _storage.Target.ChecksumMatchesAsync(document, name, checksum, cancellationToken); - - public Task ReadStreamAsync(string name, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(name, cancellationToken); - - public Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(project, name, cancellationToken); - - public Task ReadStreamAsync(Document document, string name, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(document, name, cancellationToken); - - public Task ReadStreamAsync(string name, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(name, checksum, cancellationToken); - - public Task ReadStreamAsync(Project project, string name, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(project, name, checksum, cancellationToken); - - public Task ReadStreamAsync(Document document, string name, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(document, name, checksum, cancellationToken); - - public Task ReadStreamAsync(ProjectKey project, string name, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(project, name, checksum, cancellationToken); - - public Task ReadStreamAsync(DocumentKey document, string name, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(document, name, checksum, cancellationToken); - - public Task WriteStreamAsync(string name, Stream stream, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(name, stream, cancellationToken); - - public Task WriteStreamAsync(Project project, string name, Stream stream, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(project, name, stream, cancellationToken); - - public Task WriteStreamAsync(Document document, string name, Stream stream, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(document, name, stream, cancellationToken); - - public Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(name, stream, checksum, cancellationToken); - - public Task WriteStreamAsync(Project project, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(project, name, stream, checksum, cancellationToken); - - public Task WriteStreamAsync(Document document, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(document, name, stream, checksum, cancellationToken); - - public Task WriteStreamAsync(ProjectKey projectKey, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(projectKey, name, stream, checksum, cancellationToken); - - public Task WriteStreamAsync(DocumentKey documentKey, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(documentKey, name, stream, checksum, cancellationToken); - } } diff --git a/src/Workspaces/Core/Portable/Storage/LegacyPersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/LegacyPersistentStorageService.cs index e9a3d231cd09f..4418282eb1cb0 100644 --- a/src/Workspaces/Core/Portable/Storage/LegacyPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/LegacyPersistentStorageService.cs @@ -36,5 +36,5 @@ public LegacyPersistentStorageService() } public IPersistentStorage GetStorage(Solution solution) - => NoOpPersistentStorage.GetOrThrow(throwOnFailure: false); + => NoOpPersistentStorage.GetOrThrow(SolutionKey.ToSolutionKey(solution), throwOnFailure: false); } diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/AbstractPersistentStorageService+SQLiteTestAccessor.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/AbstractPersistentStorageService+SQLiteTestAccessor.cs new file mode 100644 index 0000000000000..57f78ef598176 --- /dev/null +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/AbstractPersistentStorageService+SQLiteTestAccessor.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 Microsoft.CodeAnalysis.SQLite.v2; + +namespace Microsoft.CodeAnalysis.Storage; + +internal partial class AbstractPersistentStorageService +{ + internal TestAccessor GetTestAccessor() + => new(this); + + internal readonly struct TestAccessor(AbstractPersistentStorageService service) + { + public void Shutdown() + { + (service._currentPersistentStorage as SQLitePersistentStorage)?.DatabaseOwnership.Dispose(); + service._currentPersistentStorage = null; + } + } +} diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs index 440b6409cd496..c710a86dd43d2 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs @@ -21,16 +21,16 @@ namespace Microsoft.CodeAnalysis.SQLite.v2.Interop; /// Encapsulates a connection to a sqlite database. On construction an attempt will be made /// to open the DB if it exists, or create it if it does not. /// -/// Connections are considered relatively heavyweight and are pooled until the -/// is d. Connections can be used by different threads, -/// but only as long as they are used by one thread at a time. They are not safe for concurrent -/// use by several threads. +/// Connections are considered relatively heavyweight and are pooled (see ). Connections can be used by different +/// threads, but only as long as they are used by one thread at a time. They are not safe for concurrent use by several +/// threads. /// /// s can be created through the user of . /// These statements are cached for the lifetime of the connection and are only finalized /// (i.e. destroyed) when the connection is closed. /// -internal class SqlConnection +internal sealed class SqlConnection { // Cached UTF-8 (and null terminated) versions of the common strings we need to pass to sqlite. Used to prevent // having to convert these names to/from utf16 to UTF-8 on every call. Sqlite requires these be null terminated. diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool+PooledConnection.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool+PooledConnection.cs deleted file mode 100644 index 1b3750e7a9289..0000000000000 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool+PooledConnection.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Microsoft.CodeAnalysis.SQLite.v2.Interop; - -namespace Microsoft.CodeAnalysis.SQLite.v2; - -internal partial class SQLiteConnectionPool -{ - internal readonly struct PooledConnection(SQLiteConnectionPool connectionPool, SqlConnection sqlConnection) : IDisposable - { - public readonly SqlConnection Connection = sqlConnection; - - public void Dispose() - => connectionPool.ReleaseConnection(Connection); - } -} diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPoolService.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPoolService.cs deleted file mode 100644 index bafe6a92c2e1f..0000000000000 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPoolService.cs +++ /dev/null @@ -1,159 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Composition; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.SQLite.v2.Interop; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.SQLite.v2; - -[Export] -[Shared] -internal sealed class SQLiteConnectionPoolService : IDisposable -{ - private const string LockFile = "db.lock"; - - private readonly object _gate = new(); - - /// - /// Maps from database file path to connection pool. - /// - /// - /// Access to this field is synchronized through . - /// - private readonly Dictionary> _connectionPools = []; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SQLiteConnectionPoolService() - { - } - - /// - /// Use a to simulate a reader-writer lock. - /// Read operations are performed on the - /// and writes are performed on the . - /// - /// We use this as a condition of using the in-memory shared-cache sqlite DB. This DB - /// doesn't busy-wait when attempts are made to lock the tables in it, which can lead to - /// deadlocks. Specifically, consider two threads doing the following: - /// - /// Thread A starts a transaction that starts as a reader, and later attempts to perform a - /// write. Thread B is a writer (either started that way, or started as a reader and - /// promoted to a writer first). B holds a RESERVED lock, waiting for readers to clear so it - /// can start writing. A holds a SHARED lock (it's a reader) and tries to acquire RESERVED - /// lock (so it can start writing). The only way to make progress in this situation is for - /// one of the transactions to roll back. No amount of waiting will help, so when SQLite - /// detects this situation, it doesn't honor the busy timeout. - /// - /// To prevent this scenario, we control our access to the db explicitly with operations that - /// can concurrently read, and operations that exclusively write. - /// - /// All code that reads or writes from the db should go through this. - /// - public ConcurrentExclusiveSchedulerPair Scheduler { get; } = new(); - - public void Dispose() - { - lock (_gate) - { - foreach (var (_, pool) in _connectionPools) - pool.Dispose(); - - _connectionPools.Clear(); - } - } - - public ReferenceCountedDisposable? TryOpenDatabase( - string databaseFilePath, - IPersistentStorageFaultInjector? faultInjector, - Action initializer, - CancellationToken cancellationToken) - { - lock (_gate) - { - if (_connectionPools.TryGetValue(databaseFilePath, out var pool)) - { - return pool.TryAddReference() ?? throw ExceptionUtilities.Unreachable(); - } - - // try to get db ownership lock. if someone else already has the lock. it will throw - var ownershipLock = TryGetDatabaseOwnership(databaseFilePath); - if (ownershipLock == null) - { - return null; - } - - try - { - pool = new ReferenceCountedDisposable( - new SQLiteConnectionPool(this, faultInjector, databaseFilePath, ownershipLock)); - - pool.Target.Initialize(initializer, cancellationToken); - - // Place the initial ownership reference in _connectionPools, and return another - _connectionPools.Add(databaseFilePath, pool); - return pool.TryAddReference() ?? throw ExceptionUtilities.Unreachable(); - } - catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex, cancellationToken)) - { - if (pool is not null) - { - // Dispose of the connection pool, releasing the ownership lock. - pool.Dispose(); - } - else - { - // The storage was not created so nothing owns the lock. - // Dispose the lock to allow reuse. - ownershipLock.Dispose(); - } - - throw; - } - } - } - - /// - /// Returns null in the case where an IO exception prevented us from being able to acquire - /// the db lock file. - /// - private static IDisposable? TryGetDatabaseOwnership(string databaseFilePath) - { - return IOUtilities.PerformIO(() => - { - // make sure directory exist first. - EnsureDirectory(databaseFilePath); - - var directoryName = Path.GetDirectoryName(databaseFilePath); - Contract.ThrowIfNull(directoryName); - - return File.Open( - Path.Combine(directoryName, LockFile), - FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); - }, defaultValue: null); - } - - private static void EnsureDirectory(string databaseFilePath) - { - var directory = Path.GetDirectoryName(databaseFilePath); - Contract.ThrowIfNull(directory); - - if (Directory.Exists(directory)) - { - return; - } - - Directory.CreateDirectory(directory); - } -} diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage+PooledConnection.cs similarity index 52% rename from src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool.cs rename to src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage+PooledConnection.cs index 6d703bc07e826..bc0cd558919b9 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage+PooledConnection.cs @@ -3,63 +3,20 @@ // 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.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SQLite.v2.Interop; namespace Microsoft.CodeAnalysis.SQLite.v2; -internal sealed partial class SQLiteConnectionPool(SQLiteConnectionPoolService connectionPoolService, IPersistentStorageFaultInjector? faultInjector, string databasePath, IDisposable ownershipLock) : IDisposable +internal partial class SQLitePersistentStorage { - // We pool connections to the DB so that we don't have to take the hit of - // reconnecting. The connections also cache the prepared statements used - // to get/set data from the db. A connection is safe to use by one thread - // at a time, but is not safe for simultaneous use by multiple threads. - private readonly object _connectionGate = new(); - private readonly Stack _connectionsPool = new(); - - private readonly CancellationTokenSource _shutdownTokenSource = new(); - - internal void Initialize( - Action initializer, - CancellationToken cancellationToken) - { - // This is our startup path. No other code can be running. So it's safe for us to access a connection that - // can talk to the db without having to be on the reader/writer scheduler queue. - using var _ = GetPooledConnection(checkScheduler: false, out var connection); - - initializer(connection, cancellationToken); - } - - public void Dispose() + private readonly struct PooledConnection(SQLitePersistentStorage storage, SqlConnection sqlConnection) : IDisposable { - // Flush all pending writes so that all data our features wanted written - // are definitely persisted to the DB. - try - { - _shutdownTokenSource.Cancel(); - CloseWorker(); - } - finally - { - // let the lock go - ownershipLock.Dispose(); - } - } + public readonly SqlConnection Connection = sqlConnection; - private void CloseWorker() - { - lock (_connectionGate) - { - // Go through all our pooled connections and close them. - while (_connectionsPool.Count > 0) - { - var connection = _connectionsPool.Pop(); - connection.Close_OnlyForUseBySQLiteConnectionPool(); - } - } + public void Dispose() + => storage.ReleaseConnection(Connection); } /// @@ -70,7 +27,7 @@ private void CloseWorker() /// longer in use. In particular, make sure to avoid letting a connection lease cross an /// boundary, as it will prevent code in the asynchronous operation from using the existing connection. /// - internal PooledConnection GetPooledConnection(out SqlConnection connection) + private PooledConnection GetPooledConnection(out SqlConnection connection) => GetPooledConnection(checkScheduler: true, out connection); /// @@ -83,8 +40,8 @@ private PooledConnection GetPooledConnection(bool checkScheduler, out SqlConnect if (checkScheduler) { var scheduler = TaskScheduler.Current; - if (scheduler != connectionPoolService.Scheduler.ConcurrentScheduler && scheduler != connectionPoolService.Scheduler.ExclusiveScheduler) - throw new InvalidOperationException($"Cannot get a connection to the DB unless running on one of {nameof(SQLiteConnectionPoolService)}'s schedulers"); + if (scheduler != this.Scheduler.ConcurrentScheduler && scheduler != this.Scheduler.ExclusiveScheduler) + throw new InvalidOperationException($"Cannot get a connection to the DB unless running on one of {nameof(SQLitePersistentStorage)}'s schedulers"); } var result = new PooledConnection(this, GetConnection()); @@ -97,14 +54,12 @@ private SqlConnection GetConnection() lock (_connectionGate) { // If we have an available connection, just return that. - if (_connectionsPool.Count > 0) - { - return _connectionsPool.Pop(); - } + if (_connectionsPool.TryPop(out var connection)) + return connection; } // Otherwise create a new connection. - return SqlConnection.Create(faultInjector, databasePath); + return SqlConnection.Create(_faultInjector, this.DatabaseFile); } private void ReleaseConnection(SqlConnection connection) diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.Accessor.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.Accessor.cs index 4ea118171b220..0765d555aa450 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.Accessor.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.Accessor.cs @@ -163,13 +163,13 @@ private Optional ReadColumn( // We're reading. All current scenarios have this happening under the concurrent/read-only scheduler. // If this assert fires either a bug has been introduced, or there is a valid scenario for a writing // codepath to read a column and this assert should be adjusted. - Contract.ThrowIfFalse(TaskScheduler.Current == Storage._connectionPoolService.Scheduler.ConcurrentScheduler); + Contract.ThrowIfFalse(TaskScheduler.Current == this.Storage.Scheduler.ConcurrentScheduler); cancellationToken.ThrowIfCancellationRequested(); if (!Storage._shutdownTokenSource.IsCancellationRequested) { - using var _ = Storage._connectionPool.Target.GetPooledConnection(out var connection); + using var _ = this.Storage.GetPooledConnection(out var connection); // We're in the reading-only scheduler path, so we can't allow TryGetDatabaseId to write. Note that // this is ok, and actually provides the semantics we want. Specifically, we can be trying to read @@ -222,13 +222,13 @@ public Task WriteStreamAsync(TKey key, string name, Stream stream, Checksu private bool WriteStream(TKey key, string dataName, Stream stream, Checksum? checksum, CancellationToken cancellationToken) { // We're writing. This better always be under the exclusive scheduler. - Contract.ThrowIfFalse(TaskScheduler.Current == Storage._connectionPoolService.Scheduler.ExclusiveScheduler); + Contract.ThrowIfFalse(TaskScheduler.Current == this.Storage.Scheduler.ExclusiveScheduler); cancellationToken.ThrowIfCancellationRequested(); if (!Storage._shutdownTokenSource.IsCancellationRequested) { - using var _ = Storage._connectionPool.Target.GetPooledConnection(out var connection); + using var _ = this.Storage.GetPooledConnection(out var connection); // Determine the appropriate data-id to store this stream at. We already are running // with an exclusive write lock on the DB, so it's safe for us to write the data id to @@ -361,7 +361,7 @@ private void InsertOrReplaceBlobIntoWriteCache( ReadOnlySpan dataBytes) { // We're writing. This better always be under the exclusive scheduler. - Contract.ThrowIfFalse(TaskScheduler.Current == Storage._connectionPoolService.Scheduler.ExclusiveScheduler); + Contract.ThrowIfFalse(TaskScheduler.Current == this.Storage.Scheduler.ExclusiveScheduler); using (var resettableStatement = connection.GetResettableStatement( _insert_or_replace_into_writecache_table_values_0primarykey_1checksum_2data)) diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs index 5d31591fd3d4a..9ea33b38eff0f 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.SQLite.Interop; using Microsoft.CodeAnalysis.SQLite.v2.Interop; using Microsoft.CodeAnalysis.Storage; @@ -23,15 +25,30 @@ namespace Microsoft.CodeAnalysis.SQLite.v2; /// internal sealed partial class SQLitePersistentStorage : AbstractPersistentStorage { + private const string LockFile = "db.lock"; + private readonly CancellationTokenSource _shutdownTokenSource = new(); - private readonly SolutionKey _solutionKey; private readonly string _solutionDirectory; - private readonly SQLiteConnectionPoolService _connectionPoolService; - private readonly ReferenceCountedDisposable _connectionPool; + // We pool connections to the DB so that we don't have to take the hit of + // reconnecting. The connections also cache the prepared statements used + // to get/set data from the db. A connection is safe to use by one thread + // at a time, but is not safe for simultaneous use by multiple threads. + private readonly object _connectionGate = new(); + private readonly Stack _connectionsPool = new(); private readonly Action _flushInMemoryDataToDisk; + /// + /// Lock file that ensures only one database is made per process per solution. + /// + public readonly IDisposable DatabaseOwnership; + + /// + /// For testing purposes. Allows us to test what happens when we fail to acquire the db lock file. + /// + private readonly IPersistentStorageFaultInjector? _faultInjector; + // Accessors that allow us to retrieve/store data into specific DB tables. The // core Accessor type has logic that we to share across all reading/writing, while // the derived types contain only enough logic to specify how to read/write from @@ -47,31 +64,48 @@ internal sealed partial class SQLitePersistentStorage : AbstractPersistentStorag private readonly string _select_star_from_string_table_where_0_limit_one = $"select * from {StringInfoTableName} where ({DataColumnName} = ?) limit 1"; private readonly string _select_star_from_string_table = $"select * from {StringInfoTableName}"; + /// + /// Use a to simulate a reader-writer lock. + /// Read operations are performed on the + /// and writes are performed on the . + /// + /// We use this as a condition of using the in-memory shared-cache sqlite DB. This DB + /// doesn't busy-wait when attempts are made to lock the tables in it, which can lead to + /// deadlocks. Specifically, consider two threads doing the following: + /// + /// Thread A starts a transaction that starts as a reader, and later attempts to perform a + /// write. Thread B is a writer (either started that way, or started as a reader and + /// promoted to a writer first). B holds a RESERVED lock, waiting for readers to clear so it + /// can start writing. A holds a SHARED lock (it's a reader) and tries to acquire RESERVED + /// lock (so it can start writing). The only way to make progress in this situation is for + /// one of the transactions to roll back. No amount of waiting will help, so when SQLite + /// detects this situation, it doesn't honor the busy timeout. + /// + /// To prevent this scenario, we control our access to the db explicitly with operations that + /// can concurrently read, and operations that exclusively write. + /// + /// All code that reads or writes from the db should go through this. + /// + private ConcurrentExclusiveSchedulerPair Scheduler { get; } = new(); + private SQLitePersistentStorage( - SQLiteConnectionPoolService connectionPoolService, SolutionKey solutionKey, string workingFolderPath, string databaseFile, IAsynchronousOperationListener asyncListener, - IPersistentStorageFaultInjector? faultInjector) - : base(workingFolderPath, solutionKey.FilePath!, databaseFile) + IPersistentStorageFaultInjector? faultInjector, + IDisposable databaseOwnership) + : base(solutionKey, workingFolderPath, databaseFile) { Contract.ThrowIfNull(solutionKey.FilePath); - _solutionKey = solutionKey; _solutionDirectory = PathUtilities.GetDirectoryName(solutionKey.FilePath); - _connectionPoolService = connectionPoolService; _solutionAccessor = new SolutionAccessor(this); _projectAccessor = new ProjectAccessor(this); _documentAccessor = new DocumentAccessor(this); - // This assignment violates the declared non-nullability of _connectionPool, but the caller ensures that - // the constructed object is only used if the nullability post-conditions are met. - _connectionPool = connectionPoolService.TryOpenDatabase( - databaseFile, - faultInjector, - Initialize, - CancellationToken.None)!; + _faultInjector = faultInjector; + DatabaseOwnership = databaseOwnership; // Create a delay to batch up requests to flush. We'll won't flush more than every FlushAllDelayMS. _flushInMemoryDataToDisk = FlushInMemoryDataToDisk; @@ -83,49 +117,52 @@ private SQLitePersistentStorage( } public static SQLitePersistentStorage? TryCreate( - SQLiteConnectionPoolService connectionPoolService, SolutionKey solutionKey, string workingFolderPath, string databaseFile, IAsynchronousOperationListener asyncListener, IPersistentStorageFaultInjector? faultInjector) { - var sqlStorage = new SQLitePersistentStorage( - connectionPoolService, solutionKey, workingFolderPath, databaseFile, asyncListener, faultInjector); - if (sqlStorage._connectionPool is null) - { - // The connection pool failed to initialize + var databaseOwnership = TryGetDatabaseOwnership(databaseFile); + if (databaseOwnership is null) return null; - } - return sqlStorage; + var storage = new SQLitePersistentStorage( + solutionKey, workingFolderPath, databaseFile, asyncListener, faultInjector, databaseOwnership); + storage.Initialize(); + return storage; } - public override void Dispose() + /// + /// Returns null in the case where an IO exception prevented us from being able to acquire + /// the db lock file. + /// + private static IDisposable? TryGetDatabaseOwnership(string databaseFilePath) { - var task = DisposeAsync().AsTask(); - task.Wait(); - } + return IOUtilities.PerformIO(() => + { + // make sure directory exist first. + EnsureDirectory(databaseFilePath); - public override async ValueTask DisposeAsync() - { - try + var directoryName = Path.GetDirectoryName(databaseFilePath); + Contract.ThrowIfNull(directoryName); + + return File.Open( + Path.Combine(directoryName, LockFile), + FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + }, defaultValue: null); + + static void EnsureDirectory(string databaseFilePath) { - // Flush all pending writes so that all data our features wanted written are definitely - // persisted to the DB. - try - { - await FlushWritesOnCloseAsync().ConfigureAwait(false); - } - catch (Exception e) + var directory = Path.GetDirectoryName(databaseFilePath); + Contract.ThrowIfNull(directory); + + if (Directory.Exists(directory)) { - // Flushing may fail. We still have to close all our connections. - StorageDatabaseLogger.LogException(e); + return; } - } - finally - { - _connectionPool.Dispose(); + + Directory.CreateDirectory(directory); } } @@ -142,17 +179,11 @@ public static KeyValueLogMessage GetLogMessage(SqlException exception) d["Message"] = exception.Message; }); - private void Initialize(SqlConnection connection, CancellationToken cancellationToken) + private void Initialize() { - if (cancellationToken.IsCancellationRequested) - { - // Someone tried to get a connection *after* a call to Dispose the storage system - // happened. That should never happen. We only Dispose when the last ref to the - // storage system goes away. Once that happens, it's an error for there to be any - // future or existing consumers of the storage service. So nothing should be doing - // anything that wants to get an connection. - throw new InvalidOperationException(); - } + // This is our startup path. No other code can be running. So it's safe for us to access a connection that can + // talk to the db without having to be on the reader/writer scheduler queue. + using var _ = GetPooledConnection(checkScheduler: false, out var connection); // Ensure the database has tables for the types we care about. diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs index fe7686516eb3d..9f1570f00f3b6 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs @@ -17,7 +17,6 @@ namespace Microsoft.CodeAnalysis.SQLite.v2; internal sealed class SQLitePersistentStorageService( - SQLiteConnectionPoolService connectionPoolService, IPersistentStorageConfiguration configuration, IAsynchronousOperationListener asyncListener) : AbstractPersistentStorageService(configuration), IWorkspaceService { @@ -25,13 +24,12 @@ internal sealed class SQLitePersistentStorageService( [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class ServiceFactory( - SQLiteConnectionPoolService connectionPoolService, IAsynchronousOperationListenerProvider asyncOperationListenerProvider) : IWorkspaceServiceFactory { private readonly IAsynchronousOperationListener _asyncListener = asyncOperationListenerProvider.GetListener(FeatureAttribute.PersistentStorage); public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new SQLitePersistentStorageService(connectionPoolService, workspaceServices.GetRequiredService(), _asyncListener); + => new SQLitePersistentStorageService(workspaceServices.GetRequiredService(), _asyncListener); } private const string StorageExtension = "sqlite3"; @@ -61,18 +59,6 @@ private static bool TryInitializeLibrariesLazy() return true; } - private readonly IPersistentStorageFaultInjector? _faultInjector; - - public SQLitePersistentStorageService( - SQLiteConnectionPoolService connectionPoolService, - IPersistentStorageConfiguration configuration, - IAsynchronousOperationListener asyncListener, - IPersistentStorageFaultInjector? faultInjector) - : this(connectionPoolService, configuration, asyncListener) - { - _faultInjector = faultInjector; - } - protected override string GetDatabaseFilePath(string workingFolderPath) { Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(workingFolderPath)); @@ -80,7 +66,7 @@ protected override string GetDatabaseFilePath(string workingFolderPath) } protected override ValueTask TryOpenDatabaseAsync( - SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, CancellationToken cancellationToken) + SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, IPersistentStorageFaultInjector? faultInjector, CancellationToken cancellationToken) { if (!TryInitializeLibraries()) { @@ -89,17 +75,13 @@ protected override string GetDatabaseFilePath(string workingFolderPath) } if (solutionKey.FilePath == null) - return new(NoOpPersistentStorage.GetOrThrow(Configuration.ThrowOnFailure)); + return new(NoOpPersistentStorage.GetOrThrow(solutionKey, Configuration.ThrowOnFailure)); return new(SQLitePersistentStorage.TryCreate( - connectionPoolService, solutionKey, workingFolderPath, databaseFilePath, asyncListener, - _faultInjector)); + faultInjector)); } - - // Error occurred when trying to open this DB. Try to remove it so we can create a good DB. - protected override bool ShouldDeleteDatabase(Exception exception) => true; } diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs index 1988badfbe5db..e0d6ca9943104 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs @@ -29,47 +29,17 @@ private async ValueTask FlushInMemoryDataToDiskIfNotShutdownAsync(CancellationTo await PerformWriteAsync(_flushInMemoryDataToDisk, cancellationToken).ConfigureAwait(false); } - private Task FlushWritesOnCloseAsync() - { - // Issue a write task to write this all out to disk. - // - // Note: this only happens on close, so we don't try to avoid allocations here. - - return PerformWriteAsync( - () => - { - // Perform the actual write while having exclusive access to the scheduler. - FlushInMemoryDataToDisk(); - - // Now that we've done this, definitely cancel any further work. From this point on, it is now - // invalid for any codepaths to try to acquire a db connection for any purpose (beyond us - // disposing things below). - // - // This will also ensure that if we have a bg flush task still pending, when it wakes up it will - // see that we're shutdown and not proceed (and importantly won't acquire a connection). Because - // both the bg task and us run serialized, there is no way for it to miss this token - // cancellation. If it runs after us, then it sees this. If it runs before us, then we just - // block until it finishes. - // - // We don't have to worry about reads/writes getting connections either. - // The only way we can get disposed in the first place is if every user of this storage instance - // has released their ref on us. In that case, it would be an error on their part to ever try to - // read/write after releasing us. - _shutdownTokenSource.Cancel(); - }, CancellationToken.None); - } - private void FlushInMemoryDataToDisk() { // We're writing. This better always be under the exclusive scheduler. - Contract.ThrowIfFalse(TaskScheduler.Current == _connectionPoolService.Scheduler.ExclusiveScheduler); + Contract.ThrowIfFalse(TaskScheduler.Current == this.Scheduler.ExclusiveScheduler); // Don't flush from a bg task if we've been asked to shutdown. The shutdown logic in the storage service // will take care of the final writes to the main db. if (_shutdownTokenSource.IsCancellationRequested) return; - using var _ = _connectionPool.Target.GetPooledConnection(out var connection); + using var _ = this.GetPooledConnection(out var connection); var exception = connection.RunInTransaction(static state => { diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_SolutionSerialization.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_SolutionSerialization.cs index e17c3f8591134..5e4ef383324fb 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_SolutionSerialization.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_SolutionSerialization.cs @@ -16,13 +16,13 @@ namespace Microsoft.CodeAnalysis.SQLite.v2; internal partial class SQLitePersistentStorage { public override Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken) - => _solutionAccessor.ChecksumMatchesAsync(_solutionKey, name, checksum, cancellationToken); + => _solutionAccessor.ChecksumMatchesAsync(this.SolutionKey, name, checksum, cancellationToken); public override Task ReadStreamAsync(string name, Checksum? checksum, CancellationToken cancellationToken) - => _solutionAccessor.ReadStreamAsync(_solutionKey, name, checksum, cancellationToken); + => _solutionAccessor.ReadStreamAsync(this.SolutionKey, name, checksum, cancellationToken); public override Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _solutionAccessor.WriteStreamAsync(_solutionKey, name, stream, checksum, cancellationToken); + => _solutionAccessor.WriteStreamAsync(this.SolutionKey, name, stream, checksum, cancellationToken); private readonly record struct SolutionPrimaryKey(); diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_StringIds.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_StringIds.cs index be276efa9a39a..c9c4e8c126b2c 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_StringIds.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_StringIds.cs @@ -47,8 +47,8 @@ internal partial class SQLitePersistentStorage { // We're reading or writing. This can be under either of our schedulers. Contract.ThrowIfFalse( - TaskScheduler.Current == _connectionPoolService.Scheduler.ExclusiveScheduler || - TaskScheduler.Current == _connectionPoolService.Scheduler.ConcurrentScheduler); + TaskScheduler.Current == this.Scheduler.ExclusiveScheduler || + TaskScheduler.Current == this.Scheduler.ConcurrentScheduler); // First, check if we can find that string in the string table. var stringId = TryGetStringIdFromDatabaseWorker(connection, value, canReturnNull: true); @@ -65,7 +65,7 @@ internal partial class SQLitePersistentStorage return null; // We're writing. This better always be under the exclusive scheduler. - Contract.ThrowIfFalse(TaskScheduler.Current == _connectionPoolService.Scheduler.ExclusiveScheduler); + Contract.ThrowIfFalse(TaskScheduler.Current == this.Scheduler.ExclusiveScheduler); // The string wasn't in the db string table. Add it. Note: this may fail if some // other thread/process beats us there as this table has a 'unique' constraint on the 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 9a278af3c2135..0ff47ae62e10f 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs @@ -34,10 +34,11 @@ private Task PerformReadAsync(Func func, // 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. + // ⚠ DO NOT AWAIT INSIDE THE USING BLOCK LEXICALLY (it's fine to await within the call to PerformTaskAsync). 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); + return PerformTaskAsync(func, arg, this.Scheduler.ConcurrentScheduler, cancellationToken); } // Write tasks go to the exclusive-scheduler so they run exclusively of all other threading @@ -48,10 +49,11 @@ public Task PerformWriteAsync(Func func, // 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. + // ⚠ DO NOT AWAIT INSIDE THE USING BLOCK LEXICALLY (it's fine to await within the call to PerformTaskAsync). 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); + return PerformTaskAsync(func, arg, this.Scheduler.ExclusiveScheduler, cancellationToken); } public Task PerformWriteAsync(Action action, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs b/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs index 13b86fe85581e..4552c97465f7d 100644 --- a/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs +++ b/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs @@ -50,6 +50,8 @@ public static class WellKnownTags public const string Error = nameof(Error); public const string Warning = nameof(Warning); + internal const string Deprecated = nameof(Deprecated); + internal const string StatusInformation = nameof(StatusInformation); internal const string AddReference = nameof(AddReference); diff --git a/src/Workspaces/Core/Portable/TaskList/TaskListItemDescriptor.cs b/src/Workspaces/Core/Portable/TaskList/TaskListItemDescriptor.cs index b2b45628fd3fb..82e8a8d302978 100644 --- a/src/Workspaces/Core/Portable/TaskList/TaskListItemDescriptor.cs +++ b/src/Workspaces/Core/Portable/TaskList/TaskListItemDescriptor.cs @@ -58,6 +58,6 @@ public static ImmutableArray Parse(ImmutableArray /// Provides access to posting telemetry events or adding information -/// to aggregated telemetry events. +/// to aggregated telemetry events. Posts pending telemetry at 30 +/// minute intervals. /// internal static class TelemetryLogging { private static ITelemetryLogProvider? s_logProvider; + private static AsyncBatchingWorkQueue? s_postTelemetryQueue; public const string KeyName = "Name"; public const string KeyValue = "Value"; public const string KeyLanguageName = "LanguageName"; public const string KeyMetricName = "MetricName"; - public static void SetLogProvider(ITelemetryLogProvider logProvider) + public static event EventHandler? Flushed; + + public static void SetLogProvider(ITelemetryLogProvider logProvider, IAsynchronousOperationListener asyncListener) { s_logProvider = logProvider; + + InterlockedOperations.Initialize(ref s_postTelemetryQueue, () => + new AsyncBatchingWorkQueue( + TimeSpan.FromMinutes(30), + PostCollectedTelemetryAsync, + asyncListener, + CancellationToken.None)); + + // Add the initial item to the queue to ensure later processing. + s_postTelemetryQueue?.AddWork(); } /// @@ -112,5 +130,17 @@ public static void LogAggregated(FunctionId functionId, KeyValueLogMessage logMe public static void Flush() { s_logProvider?.Flush(); + + Flushed?.Invoke(null, EventArgs.Empty); + } + + private static ValueTask PostCollectedTelemetryAsync(CancellationToken cancellationToken) + { + Flush(); + + // Ensure PostCollectedTelemetryAsync will get fired again after the collection period. + s_postTelemetryQueue?.AddWork(); + + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs new file mode 100644 index 0000000000000..9918414654ea1 --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +internal sealed partial class TemporaryStorageService +{ + private unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength + { + private char* _position; + private readonly char* _end; + + public DirectMemoryAccessStreamReader(char* src, int length) + : base(length) + { + RoslynDebug.Assert(src != null); + RoslynDebug.Assert(length >= 0); + + _position = src; + _end = _position + length; + } + + public override int Peek() + { + if (_position >= _end) + { + return -1; + } + + return *_position; + } + + public override int Read() + { + if (_position >= _end) + { + return -1; + } + + return *_position++; + } + + public override int Read(char[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0 || index >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0 || (index + count) > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + count = Math.Min(count, (int)(_end - _position)); + if (count > 0) + { + Marshal.Copy((IntPtr)_position, buffer, index, count); + _position += count; + } + + return count; + } + } +} diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs index 0d08d75a58210..e58f2b4935d22 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs @@ -22,13 +22,7 @@ internal partial class Factory( public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { var textFactory = workspaceServices.GetRequiredService(); - - // MemoryMapped files which are used by the TemporaryStorageService are present in .NET Framework (including Mono) - // and .NET Core Windows. For non-Windows .NET Core scenarios, we can return the TrivialTemporaryStorageService - // until https://github.com/dotnet/runtime/issues/30878 is fixed. - return PlatformInformation.IsWindows || PlatformInformation.IsRunningOnMono - ? new TemporaryStorageService(workspaceThreadingService, textFactory) - : TrivialTemporaryStorageService.Instance; + return new TemporaryStorageService(workspaceThreadingService, textFactory); } } } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs index 0bd1fd88552e7..0e0c87e785c27 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -7,7 +7,6 @@ using System.IO; using System.IO.MemoryMappedFiles; using System.Runtime; -using System.Runtime.InteropServices; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -22,54 +21,38 @@ internal partial class TemporaryStorageService /// metadata dll shadow copy. shared view will help those cases. /// /// - /// Instances of this class should be disposed when they are no longer needed. After disposing this - /// instance, it should no longer be used. However, streams obtained through - /// or will not be invalidated until they are disposed independently (which - /// may occur before or after the is disposed. - /// - /// This class and its nested types have familiar APIs and predictable behavior when used in other code, - /// but are non-trivial to work on. The implementations of adhere to the best - /// practices described in - /// DG - /// Update: Dispose, Finalization, and Resource Management. Additional notes regarding operating system - /// behavior leveraged for efficiency are given in comments. + /// This class and its nested types have familiar APIs and predictable behavior when used in other code, but + /// are non-trivial to work on. /// - internal sealed class MemoryMappedInfo(ReferenceCountedDisposable memoryMappedFile, string name, long offset, long size) : IDisposable + internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string? name, long offset, long size) { /// /// The memory mapped file. /// - /// - /// It is possible for the file to be disposed prior to the view and/or the streams which use it. - /// However, the operating system does not actually close the views which are in use until the file handles - /// are closed as well, even if the file is disposed first. - /// - private readonly ReferenceCountedDisposable _memoryMappedFile = memoryMappedFile; + public readonly MemoryMappedFile MemoryMappedFile = memoryMappedFile; /// /// A weak reference to a read-only view for the memory mapped file. /// /// - /// This holds a weak counted reference to current , which - /// allows additional accessors for the same address space to be obtained up until the point when no - /// external code is using it. When the memory is no longer being used by any objects, the view of the memory mapped file is unmapped, - /// making the process address space it previously claimed available for other purposes. If/when it is - /// needed again, a new view is created. + /// This holds a weak counted reference to current , which allows + /// additional accessors for the same address space to be obtained up until the point when no external code is + /// using it. When the memory is no longer being used by any + /// objects, the view of the memory mapped file is unmapped, making the process address space it previously + /// claimed available for other purposes. If/when it is needed again, a new view is created. /// /// This view is read-only, so it is only used by . /// private ReferenceCountedDisposable.WeakReference _weakReadAccessor; - public MemoryMappedInfo(string name, long offset, long size) - : this(new ReferenceCountedDisposable(MemoryMappedFile.OpenExisting(name)), name, offset, size) - { - } + public static MemoryMappedInfo CreateNew(string? name, long size) + => new(MemoryMappedFile.CreateNew(name, size), name, offset: 0, size); /// - /// The name of the memory mapped file. + /// The name of the memory mapped file. Non null on systems that support named memory mapped files, null + /// otherwise.. /// - public string Name { get; } = name; + public string? Name { get; } = name; /// /// The offset into the memory mapped file of the region described by the current @@ -89,21 +72,14 @@ public MemoryMappedInfo(string name, long offset, long size) /// public UnmanagedMemoryStream CreateReadableStream() { - // Note: TryAddReference behaves according to its documentation even if the target object has been - // disposed. If it returns non-null, then the object will not be disposed before the returned - // reference is disposed (see comments on _memoryMappedFile and TryAddReference). + // Note: TryAddReference behaves according to its documentation even if the target object has been disposed. + // If it returns non-null, then the object will not be disposed before the returned reference is disposed + // (see comments on _memoryMappedFile and TryAddReference). var streamAccessor = _weakReadAccessor.TryAddReference(); if (streamAccessor == null) { var rawAccessor = RunWithCompactingGCFallback( - static info => - { - using var memoryMappedFile = info._memoryMappedFile.TryAddReference(); - if (memoryMappedFile is null) - throw new ObjectDisposedException(typeof(MemoryMappedInfo).FullName); - - return memoryMappedFile.Target.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read); - }, + static info => info.MemoryMappedFile.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read), this); streamAccessor = new ReferenceCountedDisposable(rawAccessor); _weakReadAccessor = new ReferenceCountedDisposable.WeakReference(streamAccessor); @@ -120,28 +96,20 @@ public UnmanagedMemoryStream CreateReadableStream() public Stream CreateWritableStream() { return RunWithCompactingGCFallback( - static info => - { - using var memoryMappedFile = info._memoryMappedFile.TryAddReference(); - if (memoryMappedFile is null) - throw new ObjectDisposedException(typeof(MemoryMappedInfo).FullName); - - return memoryMappedFile.Target.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write); - }, + static info => info.MemoryMappedFile.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write), this); } /// - /// Run a function which may fail with an if not enough memory is available to - /// satisfy the request. In this case, a full compacting GC pass is forced and the function is attempted - /// again. + /// Run a function which may fail with an if not enough memory is available to satisfy + /// the request. In this case, a full compacting GC pass is forced and the function is attempted again. /// /// - /// and - /// will use a native - /// memory map, which can't trigger a GC. In this case, we'd otherwise crash with OOM, so we don't care - /// about creating a UI delay with a full forced compacting GC. If it crashes the second try, it means we're - /// legitimately out of resources. + /// and will use a native memory map, + /// which can't trigger a GC. In this case, we'd otherwise crash with OOM, so we don't care about creating a UI + /// delay with a full forced compacting GC. If it crashes the second try, it means we're legitimately out of + /// resources. /// /// The type of argument to pass to the callback. /// The type returned by the function. @@ -171,42 +139,18 @@ private static void ForceCompactingGC() GC.Collect(); } - public void Dispose() - { - // See remarks on field for relation between _memoryMappedFile and the views/streams. There is no - // need to write _weakReadAccessor here since lifetime of the target is not owned by this instance. - _memoryMappedFile.Dispose(); - } - - private sealed unsafe class MemoryMappedViewUnmanagedMemoryStream : UnmanagedMemoryStream + private sealed unsafe class MemoryMappedViewUnmanagedMemoryStream( + ReferenceCountedDisposable accessor, + long length) : UnmanagedMemoryStream( + (byte*)accessor.Target.SafeMemoryMappedViewHandle.DangerousGetHandle() + accessor.Target.PointerOffset, + length) { - private readonly ReferenceCountedDisposable _accessor; - private byte* _start; - - public MemoryMappedViewUnmanagedMemoryStream(ReferenceCountedDisposable accessor, long length) - : base((byte*)accessor.Target.SafeMemoryMappedViewHandle.DangerousGetHandle() + accessor.Target.PointerOffset, length) - { - _accessor = accessor; - _start = this.PositionPointer; - } - protected override void Dispose(bool disposing) { base.Dispose(disposing); - if (disposing) - { - _accessor.Dispose(); - } - - _start = null; + accessor.Dispose(); } - - /// - /// Get underlying native memory directly. - /// - public IntPtr GetPointer() - => (IntPtr)_start; } } } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs new file mode 100644 index 0000000000000..52d97d1bd762c --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.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.IO; +using System.IO.MemoryMappedFiles; +using System.Threading; +using Microsoft.CodeAnalysis.Internal.Log; + +namespace Microsoft.CodeAnalysis.Host; + +internal sealed partial class TemporaryStorageService +{ + public sealed class TemporaryStorageStreamHandle( + MemoryMappedFile memoryMappedFile, + TemporaryStorageIdentifier identifier) + : ITemporaryStorageStreamHandle + { + public TemporaryStorageIdentifier Identifier => identifier; + + public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) + { + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var info = new MemoryMappedInfo(memoryMappedFile, Identifier.Name, Identifier.Offset, Identifier.Size); + return info.CreateReadableStream(); + } + } + } +} diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs new file mode 100644 index 0000000000000..56a205c39481b --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -0,0 +1,302 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.IO.MemoryMappedFiles; +using System.Runtime.Versioning; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Temporarily stores text and streams in memory mapped files. +/// +#if NETCOREAPP +[SupportedOSPlatform("windows")] +#endif +internal sealed partial class TemporaryStorageService : ITemporaryStorageServiceInternal +{ + /// + /// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other storage + /// units. + /// + /// + /// The value of 256k reduced the number of files dumped to separate memory mapped files by 60% compared to + /// the next lower power-of-2 size for Roslyn.sln itself. + /// + /// + private const long SingleFileThreshold = 256 * 1024; + + /// + /// The size in bytes of a memory mapped file created to store multiple temporary objects. + /// + /// + /// This value (8mb) creates roughly 35 memory mapped files (around 300MB) to store the contents of all of + /// Roslyn.sln a snapshot. This keeps the data safe, so that we can drop it from memory when not needed, but + /// reconstitute the contents we originally had in the snapshot in case the original files change on disk. + /// + /// + private const long MultiFileBlockSize = SingleFileThreshold * 32; + + private readonly IWorkspaceThreadingService? _workspaceThreadingService; + private readonly ITextFactoryService _textFactory; + + /// + /// The synchronization object for accessing the memory mapped file related fields (indicated in the remarks + /// of each field). + /// + /// + /// PERF DEV NOTE: A concurrent (but complex) implementation of this type with identical semantics is + /// available in source control history. The use of exclusive locks was not causing any measurable + /// performance overhead even on 28-thread machines at the time this was written. + /// + private readonly object _gate = new(); + + /// + /// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer + /// allocation until space is no longer available in it. Access should be synchronized on + /// + private MemoryMappedFile? _fileReference; + + /// The name of the current memory mapped file for multiple storage units. Access should be synchronized on + /// + /// + private string? _name; + + /// The total size of the current memory mapped file for multiple storage units. Access should be + /// synchronized on + /// + private long _fileSize; + + /// + /// The offset into the current memory mapped file where the next storage unit can be held. Access should be + /// synchronized on . + /// + /// + private long _offset; + + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + private TemporaryStorageService(IWorkspaceThreadingService? workspaceThreadingService, ITextFactoryService textFactory) + { + _workspaceThreadingService = workspaceThreadingService; + _textFactory = textFactory; + } + + ITemporaryStorageTextHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + => WriteToTemporaryStorage(text, cancellationToken); + + async Task ITemporaryStorageServiceInternal.WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + => await WriteToTemporaryStorageAsync(text, cancellationToken).ConfigureAwait(false); + + public TemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + { + var memoryMappedInfo = WriteToMemoryMappedFile(); + var identifier = new TemporaryStorageIdentifier(memoryMappedInfo.Name, memoryMappedInfo.Offset, memoryMappedInfo.Size); + return new(this, memoryMappedInfo.MemoryMappedFile, identifier, text.ChecksumAlgorithm, text.Encoding, text.GetContentHash()); + + MemoryMappedInfo WriteToMemoryMappedFile() + { + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteText, cancellationToken)) + { + // the method we use to get text out of SourceText uses Unicode (2bytes per char). + var size = Encoding.Unicode.GetMaxByteCount(text.Length); + var memoryMappedInfo = this.CreateTemporaryStorage(size); + + // Write the source text out as Unicode. We expect that to be cheap. + using var stream = memoryMappedInfo.CreateWritableStream(); + { + using var writer = new StreamWriter(stream, Encoding.Unicode); + text.Write(writer, cancellationToken); + } + + return memoryMappedInfo; + } + } + } + + public async Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + { + if (this._workspaceThreadingService is { IsOnMainThread: true }) + { + await Task.Yield().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + return WriteToTemporaryStorage(text, cancellationToken); + } + + ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + => WriteToTemporaryStorage(stream, cancellationToken); + + public TemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + { + stream.Position = 0; + var memoryMappedInfo = WriteToMemoryMappedFile(); + var identifier = new TemporaryStorageIdentifier(memoryMappedInfo.Name, memoryMappedInfo.Offset, memoryMappedInfo.Size); + return new(memoryMappedInfo.MemoryMappedFile, identifier); + + MemoryMappedInfo WriteToMemoryMappedFile() + { + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) + { + var size = stream.Length; + var memoryMappedInfo = this.CreateTemporaryStorage(size); + using var viewStream = memoryMappedInfo.CreateWritableStream(); + { + using var pooledObject = SharedPools.ByteArray.GetPooledObject(); + var buffer = pooledObject.Object; + while (true) + { + var count = stream.Read(buffer, 0, buffer.Length); + if (count == 0) + break; + + viewStream.Write(buffer, 0, count); + } + } + + return memoryMappedInfo; + } + } + } + + internal static TemporaryStorageStreamHandle GetStreamHandle(TemporaryStorageIdentifier storageIdentifier) + { + Contract.ThrowIfNull(storageIdentifier.Name, $"{nameof(GetStreamHandle)} should only be called for VS on Windows (where named memory mapped files as supported)"); + var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); + return new(memoryMappedFile, storageIdentifier); + } + + internal TemporaryStorageTextHandle GetTextHandle( + TemporaryStorageIdentifier storageIdentifier, + SourceHashAlgorithm checksumAlgorithm, + Encoding? encoding, + ImmutableArray contentHash) + { + Contract.ThrowIfNull(storageIdentifier.Name, $"{nameof(GetTextHandle)} should only be called for VS on Windows (where named memory mapped files as supported)"); + var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); + return new(this, memoryMappedFile, storageIdentifier, checksumAlgorithm, encoding, contentHash); + } + + /// + /// Allocate shared storage of a specified size. + /// + /// + /// "Small" requests are fulfilled from oversized memory mapped files which support several individual + /// storage units. Larger requests are allocated in their own memory mapped files. + /// + /// The size of the shared storage block to allocate. + /// A describing the allocated block. + private MemoryMappedInfo CreateTemporaryStorage(long size) + { + // Larger blocks are allocated separately + if (size >= SingleFileThreshold) + return MemoryMappedInfo.CreateNew(CreateUniqueName(size), size: size); + + lock (_gate) + { + // Obtain a reference to the memory mapped file, creating one if necessary. If a reference counted + // handle to a memory mapped file is obtained in this section, it must either be disposed before + // returning or returned to the caller who will own it through the MemoryMappedInfo. + var reference = _fileReference; + if (reference == null || _offset + size > _fileSize) + { + var mapName = CreateUniqueName(MultiFileBlockSize); + + reference = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize); + _fileReference = reference; + _name = mapName; + _fileSize = MultiFileBlockSize; + _offset = size; + return new MemoryMappedInfo(reference, _name, offset: 0, size: size); + } + else + { + // Reserve additional space in the existing storage location + _offset += size; + return new MemoryMappedInfo(reference, _name, _offset - size, size); + } + } + } + + public static string? CreateUniqueName(long size) + { + // MemoryMapped files which are used by the TemporaryStorageService are present in .NET Framework (including + // Mono) and .NET Core Windows. For non-Windows .NET Core scenarios, we return null to enable create the memory + // mapped file (just not in a way that can be shared across processes). + return PlatformInformation.IsWindows || PlatformInformation.IsRunningOnMono + ? $"Roslyn Shared File: Size={size} Id={Guid.NewGuid():N}" + : null; + } + + public sealed class TemporaryStorageTextHandle( + TemporaryStorageService storageService, + MemoryMappedFile memoryMappedFile, + TemporaryStorageIdentifier identifier, + SourceHashAlgorithm checksumAlgorithm, + Encoding? encoding, + ImmutableArray contentHash) + : ITemporaryStorageTextHandle + { + public TemporaryStorageIdentifier Identifier => identifier; + public SourceHashAlgorithm ChecksumAlgorithm => checksumAlgorithm; + public Encoding? Encoding => encoding; + public ImmutableArray ContentHash => contentHash; + + public async Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken) + { + // There is a reason for implementing it like this: proper async implementation + // that reads the underlying memory mapped file stream in an asynchronous fashion + // doesn't actually work. Windows doesn't offer + // any non-blocking way to read from a memory mapped file; the underlying memcpy + // may block as the memory pages back in and that's something you have to live + // with. Therefore, any implementation that attempts to use async will still + // always be blocking at least one threadpool thread in the memcpy in the case + // of a page fault. Therefore, if we're going to be blocking a thread, we should + // just block one thread and do the whole thing at once vs. a fake "async" + // implementation which will continue to requeue work back to the thread pool. + if (storageService._workspaceThreadingService is { IsOnMainThread: true }) + { + await Task.Yield().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + return ReadFromTemporaryStorage(cancellationToken); + } + + public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) + { + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) + { + var info = new MemoryMappedInfo(memoryMappedFile, Identifier.Name, Identifier.Offset, Identifier.Size); + using var stream = info.CreateReadableStream(); + using var reader = CreateTextReaderFromTemporaryStorage(stream); + + // we pass in encoding we got from original source text even if it is null. + return storageService._textFactory.CreateText(reader, encoding, checksumAlgorithm, cancellationToken); + } + } + + private static unsafe DirectMemoryAccessStreamReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) + { + var src = (char*)stream.PositionPointer; + + // BOM: Unicode, little endian + // Skip the BOM when creating the reader + Debug.Assert(*src == 0xFEFF); + + return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); + } + } +} diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs deleted file mode 100644 index 28692e25cb05f..0000000000000 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ /dev/null @@ -1,473 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.IO.MemoryMappedFiles; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Host; - -/// -/// Temporarily stores text and streams in memory mapped files. -/// -#if NETCOREAPP -[SupportedOSPlatform("windows")] -#endif -internal partial class TemporaryStorageService : ITemporaryStorageService2 -{ - /// - /// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other - /// storage units. - /// - /// - /// This value was arbitrarily chosen and appears to work well. Can be changed if data suggests - /// something better. - /// - /// - private const long SingleFileThreshold = 128 * 1024; - - /// - /// The size in bytes of a memory mapped file created to store multiple temporary objects. - /// - /// - /// This value was arbitrarily chosen and appears to work well. Can be changed if data suggests - /// something better. - /// - /// - private const long MultiFileBlockSize = SingleFileThreshold * 32; - - private readonly IWorkspaceThreadingService? _workspaceThreadingService; - private readonly ITextFactoryService _textFactory; - - /// - /// The synchronization object for accessing the memory mapped file related fields (indicated in the remarks - /// of each field). - /// - /// - /// PERF DEV NOTE: A concurrent (but complex) implementation of this type with identical semantics is - /// available in source control history. The use of exclusive locks was not causing any measurable - /// performance overhead even on 28-thread machines at the time this was written. - /// - private readonly object _gate = new(); - - /// - /// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer - /// allocation until space is no longer available in it. - /// - /// - /// Access should be synchronized on . - /// - private ReferenceCountedDisposable.WeakReference _weakFileReference; - - /// The name of the current memory mapped file for multiple storage units. - /// - /// Access should be synchronized on . - /// - /// - private string? _name; - - /// The total size of the current memory mapped file for multiple storage units. - /// - /// Access should be synchronized on . - /// - /// - private long _fileSize; - - /// - /// The offset into the current memory mapped file where the next storage unit can be held. - /// - /// - /// Access should be synchronized on . - /// - /// - private long _offset; - - [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] - private TemporaryStorageService(IWorkspaceThreadingService? workspaceThreadingService, ITextFactoryService textFactory) - { - _workspaceThreadingService = workspaceThreadingService; - _textFactory = textFactory; - } - - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - => new TemporaryTextStorage(this); - - public ITemporaryTextStorageInternal AttachTemporaryTextStorage(string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding) - => new TemporaryTextStorage(this, storageName, offset, size, checksumAlgorithm, encoding); - - ITemporaryStreamStorageInternal ITemporaryStorageServiceInternal.CreateTemporaryStreamStorage() - => CreateTemporaryStreamStorage(); - - internal TemporaryStreamStorage CreateTemporaryStreamStorage() - => new(this); - - public ITemporaryStreamStorageInternal AttachTemporaryStreamStorage(string storageName, long offset, long size) - => new TemporaryStreamStorage(this, storageName, offset, size); - - /// - /// Allocate shared storage of a specified size. - /// - /// - /// "Small" requests are fulfilled from oversized memory mapped files which support several individual - /// storage units. Larger requests are allocated in their own memory mapped files. - /// - /// The size of the shared storage block to allocate. - /// A describing the allocated block. - private MemoryMappedInfo CreateTemporaryStorage(long size) - { - if (size >= SingleFileThreshold) - { - // Larger blocks are allocated separately - var mapName = CreateUniqueName(size); - var storage = MemoryMappedFile.CreateNew(mapName, size); - return new MemoryMappedInfo(new ReferenceCountedDisposable(storage), mapName, offset: 0, size: size); - } - - lock (_gate) - { - // Obtain a reference to the memory mapped file, creating one if necessary. If a reference counted - // handle to a memory mapped file is obtained in this section, it must either be disposed before - // returning or returned to the caller who will own it through the MemoryMappedInfo. - var reference = _weakFileReference.TryAddReference(); - if (reference == null || _offset + size > _fileSize) - { - var mapName = CreateUniqueName(MultiFileBlockSize); - var file = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize); - - reference = new ReferenceCountedDisposable(file); - _weakFileReference = new ReferenceCountedDisposable.WeakReference(reference); - _name = mapName; - _fileSize = MultiFileBlockSize; - _offset = size; - return new MemoryMappedInfo(reference, _name, offset: 0, size: size); - } - else - { - // Reserve additional space in the existing storage location - Contract.ThrowIfNull(_name); - _offset += size; - return new MemoryMappedInfo(reference, _name, _offset - size, size); - } - } - } - - public static string CreateUniqueName(long size) - => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); - - private sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryTextStorageWithName - { - private readonly TemporaryStorageService _service; - private SourceHashAlgorithm _checksumAlgorithm; - private Encoding? _encoding; - private ImmutableArray _checksum; - private MemoryMappedInfo? _memoryMappedInfo; - - public TemporaryTextStorage(TemporaryStorageService service) - => _service = service; - - public TemporaryTextStorage(TemporaryStorageService service, string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding) - { - _service = service; - _checksumAlgorithm = checksumAlgorithm; - _encoding = encoding; - _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); - } - - // TODO: cleanup https://github.com/dotnet/roslyn/issues/43037 - // Offet, Size not accessed if Name is null - public string? Name => _memoryMappedInfo?.Name; - public long Offset => _memoryMappedInfo!.Offset; - public long Size => _memoryMappedInfo!.Size; - public SourceHashAlgorithm ChecksumAlgorithm => _checksumAlgorithm; - public Encoding? Encoding => _encoding; - - public ImmutableArray GetContentHash() - { - if (_checksum.IsDefault) - { - ImmutableInterlocked.InterlockedInitialize(ref _checksum, ReadText(CancellationToken.None).GetContentHash()); - } - - return _checksum; - } - - public void Dispose() - { - // Destructors of SafeHandle and FileStream in MemoryMappedFile - // will eventually release resources if this Dispose is not called - // explicitly - _memoryMappedInfo?.Dispose(); - - _memoryMappedInfo = null; - _encoding = null; - } - - public SourceText ReadText(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) - { - using var stream = _memoryMappedInfo.CreateReadableStream(); - using var reader = CreateTextReaderFromTemporaryStorage(stream); - - // we pass in encoding we got from original source text even if it is null. - return _service._textFactory.CreateText(reader, _encoding, _checksumAlgorithm, cancellationToken); - } - } - - public async Task ReadTextAsync(CancellationToken cancellationToken) - { - // There is a reason for implementing it like this: proper async implementation - // that reads the underlying memory mapped file stream in an asynchronous fashion - // doesn't actually work. Windows doesn't offer - // any non-blocking way to read from a memory mapped file; the underlying memcpy - // may block as the memory pages back in and that's something you have to live - // with. Therefore, any implementation that attempts to use async will still - // always be blocking at least one threadpool thread in the memcpy in the case - // of a page fault. Therefore, if we're going to be blocking a thread, we should - // just block one thread and do the whole thing at once vs. a fake "async" - // implementation which will continue to requeue work back to the thread pool. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - } - - return ReadText(cancellationToken); - } - - public void WriteText(SourceText text, CancellationToken cancellationToken) - { - if (_memoryMappedInfo != null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteText, cancellationToken)) - { - _checksumAlgorithm = text.ChecksumAlgorithm; - _encoding = text.Encoding; - - // the method we use to get text out of SourceText uses Unicode (2bytes per char). - var size = Encoding.Unicode.GetMaxByteCount(text.Length); - _memoryMappedInfo = _service.CreateTemporaryStorage(size); - - // Write the source text out as Unicode. We expect that to be cheap. - using var stream = _memoryMappedInfo.CreateWritableStream(); - using var writer = new StreamWriter(stream, Encoding.Unicode); - - text.Write(writer, cancellationToken); - } - } - - public async Task WriteTextAsync(SourceText text, CancellationToken cancellationToken) - { - // See commentary in ReadTextAsync for why this is implemented this way. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - } - - WriteText(text, cancellationToken); - } - - private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) - { - var src = (char*)stream.PositionPointer; - - // BOM: Unicode, little endian - // Skip the BOM when creating the reader - Debug.Assert(*src == 0xFEFF); - - return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); - } - } - - internal class TemporaryStreamStorage : ITemporaryStreamStorageInternal, ITemporaryStorageWithName - { - private readonly TemporaryStorageService _service; - private MemoryMappedInfo? _memoryMappedInfo; - - public TemporaryStreamStorage(TemporaryStorageService service) - => _service = service; - - public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long offset, long size) - { - _service = service; - _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); - } - - // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 - // Offset, Size is only used when Name is not null. - public string? Name => _memoryMappedInfo?.Name; - public long Offset => _memoryMappedInfo!.Offset; - public long Size => _memoryMappedInfo!.Size; - - public void Dispose() - { - // Destructors of SafeHandle and FileStream in MemoryMappedFile - // will eventually release resources if this Dispose is not called - // explicitly - _memoryMappedInfo?.Dispose(); - _memoryMappedInfo = null; - } - - Stream ITemporaryStreamStorageInternal.ReadStream(CancellationToken cancellationToken) - => ReadStream(cancellationToken); - - public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - - return _memoryMappedInfo.CreateReadableStream(); - } - } - - public Task ReadStreamAsync(CancellationToken cancellationToken = default) - { - // See commentary in ReadTextAsync for why this is implemented this way. - return Task.Factory.StartNew(() => ReadStream(cancellationToken), cancellationToken, TaskCreationOptions.None, TaskScheduler.Default); - } - - public void WriteStream(Stream stream, CancellationToken cancellationToken = default) - { - // The Wait() here will not actually block, since with useAsync: false, the - // entire operation will already be done when WaitStreamMaybeAsync completes. - WriteStreamMaybeAsync(stream, useAsync: false, cancellationToken: cancellationToken).GetAwaiter().GetResult(); - } - - public Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken = default) - => WriteStreamMaybeAsync(stream, useAsync: true, cancellationToken: cancellationToken); - - private async Task WriteStreamMaybeAsync(Stream stream, bool useAsync, CancellationToken cancellationToken) - { - if (_memoryMappedInfo != null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) - { - var size = stream.Length; - _memoryMappedInfo = _service.CreateTemporaryStorage(size); - using var viewStream = _memoryMappedInfo.CreateWritableStream(); - - var buffer = SharedPools.ByteArray.Allocate(); - try - { - while (true) - { - int count; - if (useAsync) - { - count = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); - } - else - { - count = stream.Read(buffer, 0, buffer.Length); - } - - if (count == 0) - { - break; - } - - viewStream.Write(buffer, 0, count); - } - } - finally - { - SharedPools.ByteArray.Free(buffer); - } - } - } - } -} - -internal unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength -{ - private char* _position; - private readonly char* _end; - - public DirectMemoryAccessStreamReader(char* src, int length) - : base(length) - { - RoslynDebug.Assert(src != null); - RoslynDebug.Assert(length >= 0); - - _position = src; - _end = _position + length; - } - - public override int Peek() - { - if (_position >= _end) - { - return -1; - } - - return *_position; - } - - public override int Read() - { - if (_position >= _end) - { - return -1; - } - - return *_position++; - } - - public override int Read(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (index < 0 || index >= buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (count < 0 || (index + count) > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - count = Math.Min(count, (int)(_end - _position)); - if (count > 0) - { - Marshal.Copy((IntPtr)_position, buffer, index, count); - _position += count; - } - - return count; - } -} diff --git a/src/Workspaces/Core/Portable/Utilities/SegmentedListPool.cs b/src/Workspaces/Core/Portable/Utilities/SegmentedListPool.cs index e039e9d29ac39..b78767a598ce6 100644 --- a/src/Workspaces/Core/Portable/Utilities/SegmentedListPool.cs +++ b/src/Workspaces/Core/Portable/Utilities/SegmentedListPool.cs @@ -39,7 +39,7 @@ internal static PooledObject> GetPooledList(out SegmentedLis /// are added to the list, then the singleton will be returned. Otherwise the /// instance will be returned. /// - internal static IList ComputeList( + public static IList ComputeList( Action> addItems, TArgs args, // Only used to allow type inference to work at callsite @@ -60,3 +60,13 @@ internal static IList ComputeList( return list; } } + +internal static class SegmentedListPool +{ + public static IList ComputeList( + Action> addItems, + TArgs args) + { + return SegmentedListPool.ComputeList(addItems, args, _: default); + } +} diff --git a/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs similarity index 62% rename from src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs rename to src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs index 75022a16733cf..596cf877c78ab 100644 --- a/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs @@ -9,20 +9,12 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler; -[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Default)] -[Shared] -internal sealed class DefaultDocumentTrackingService : IDocumentTrackingService +[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Default), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DefaultDocumentTrackingService() : IDocumentTrackingService { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultDocumentTrackingService() - { - } - - public bool SupportsDocumentTracking => false; - public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } public ImmutableArray GetVisibleDocuments() => []; diff --git a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs new file mode 100644 index 0000000000000..6944f40fade72 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.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.Collections.Immutable; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis; + +/// +/// Retrieves information about what documents are currently active or visible in the host workspace. Note: this +/// information is fundamentally racy (it can change directly after it is requested), and on different threads than the +/// thread that asks for it. As such, this information must only be used to provide a hint towards how a +/// feature should go about its work, it must not impact the final results that a feature produces. For example, a +/// feature is allowed to use this information to decide what order to process documents in, to try to get more relevant +/// results to a client more quickly. However, it is not allowed to use this information to decide what results to +/// return altogether. Hosts are free to implement this service to do nothing at all, always returning empty/default +/// values for the members within. As per the above, this should never affect correctness, but it may impede a +/// feature's ability to provide results in as timely a manner as possible for a client. +/// +internal interface IDocumentTrackingService : IWorkspaceService +{ + /// + /// Get the of the active document. May be null if there is no active document, the + /// active document is not in the workspace, or if this functionality is not supported by a particular host. + /// + DocumentId? TryGetActiveDocument(); + + /// + /// Get a read only collection of the s of all the visible documents in the workspace. May + /// be empty if there are no visible documents, or if this functionality is not supported by a particular host. + /// + ImmutableArray GetVisibleDocuments(); + + /// + /// Fired when the active document changes. A host is not required to support this event, even if it implements + /// . + /// + event EventHandler ActiveDocumentChanged; +} diff --git a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingServiceExtensions.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs similarity index 95% rename from src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingServiceExtensions.cs rename to src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs index 6d3b0cff1a372..9a231ee40abbc 100644 --- a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingServiceExtensions.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs @@ -2,11 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; diff --git a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/AbstractSpanMappingService.cs b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/AbstractSpanMappingService.cs index 6126b3a69c400..c8ff2bda893ec 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/AbstractSpanMappingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/AbstractSpanMappingService.cs @@ -46,6 +46,6 @@ public abstract Task> MapSpansAsync( } } - return mappedFilePathAndTextChange.ToImmutable(); + return mappedFilePathAndTextChange.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/HostWorkspaceServices.cs b/src/Workspaces/Core/Portable/Workspace/Host/HostWorkspaceServices.cs index 8e104f78ef3c4..ee33932aaf196 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/HostWorkspaceServices.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/HostWorkspaceServices.cs @@ -87,10 +87,7 @@ internal virtual ITextFactoryService TextFactory /// /// A list of language names for supported language services. /// - public virtual IEnumerable SupportedLanguages - { - get { return SpecializedCollections.EmptyEnumerable(); } - } + public virtual IEnumerable SupportedLanguages => []; /// /// Returns true if the language is supported. diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs index cbefb3294b7c1..81913cc81c0cb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs @@ -13,8 +13,9 @@ namespace Microsoft.CodeAnalysis.Host; internal abstract class AbstractPersistentStorage : IChecksummedPersistentStorage { + public SolutionKey SolutionKey { get; } + public string WorkingFolderPath { get; } - public string SolutionFilePath { get; } public string DatabaseFile { get; } public string DatabaseDirectory => Path.GetDirectoryName(DatabaseFile) ?? throw ExceptionUtilities.UnexpectedValue(DatabaseFile); @@ -22,12 +23,12 @@ internal abstract class AbstractPersistentStorage : IChecksummedPersistentStorag private bool _isDisabled; protected AbstractPersistentStorage( + SolutionKey solutionKey, string workingFolderPath, - string solutionFilePath, string databaseFile) { + this.SolutionKey = solutionKey; this.WorkingFolderPath = workingFolderPath; - this.SolutionFilePath = solutionFilePath; this.DatabaseFile = databaseFile; if (!Directory.Exists(this.DatabaseDirectory)) @@ -42,9 +43,6 @@ private bool IsDisabled protected void DisableStorage() => Volatile.Write(ref _isDisabled, true); - public abstract void Dispose(); - public abstract ValueTask DisposeAsync(); - public abstract Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken); public abstract Task ReadStreamAsync(string name, Checksum? checksum, CancellationToken cancellationToken); public abstract Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs index 27512482e6872..a0feb6908ba70 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs @@ -12,6 +12,11 @@ namespace Microsoft.CodeAnalysis.Host; internal interface IChecksummedPersistentStorage : IPersistentStorage { + /// + /// The solution this is a storage instance for. + /// + SolutionKey SolutionKey { get; } + /// /// if the data we have for the solution with the given has the /// provided . diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs index 4b81585424cf5..9d5d44929fbc8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Host; /// disposal should always be preferred as the implementation of synchronous disposal may end up blocking the caller /// on async work. /// -public interface IPersistentStorage : IDisposable, IAsyncDisposable +public interface IPersistentStorage { Task ReadStreamAsync(string name, CancellationToken cancellationToken = default); Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken = default); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs index e73c12d226a0f..823d3ae7e5cca 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs @@ -37,7 +37,7 @@ internal sealed class DefaultPersistentStorageConfiguration : IPersistentStorage /// path. For example, Base64 encoding will use / which is something that we definitely do not want /// errantly added to a path. /// - private static readonly ImmutableArray s_invalidPathChars = Path.GetInvalidPathChars().Concat('/').ToImmutableArray(); + private static readonly ImmutableArray s_invalidPathChars = [.. Path.GetInvalidPathChars(), '/']; private static readonly string s_cacheDirectory; private static readonly string s_moduleFileName; diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs index 14840b4d8f5a9..c924931a44441 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs @@ -11,27 +11,14 @@ namespace Microsoft.CodeAnalysis.Host; -internal class NoOpPersistentStorage : IChecksummedPersistentStorage +internal class NoOpPersistentStorage(SolutionKey solutionKey) : IChecksummedPersistentStorage { - private static readonly IChecksummedPersistentStorage Instance = new NoOpPersistentStorage(); + public SolutionKey SolutionKey => solutionKey; - private NoOpPersistentStorage() - { - } - - public static IChecksummedPersistentStorage GetOrThrow(bool throwOnFailure) + public static IChecksummedPersistentStorage GetOrThrow(SolutionKey solutionKey, bool throwOnFailure) => throwOnFailure ? throw new InvalidOperationException("Database was not supported") - : Instance; - - public void Dispose() - { - } - - public ValueTask DisposeAsync() - { - return ValueTaskFactory.CompletedTask; - } + : new NoOpPersistentStorage(solutionKey); public Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken) => SpecializedTasks.False; @@ -98,6 +85,6 @@ public Task WriteStreamAsync(DocumentKey documentKey, string name, Stream public readonly struct TestAccessor { - public static readonly IChecksummedPersistentStorage StorageInstance = Instance; + public static IChecksummedPersistentStorage GetStorageInstance(SolutionKey solutionKey) => new NoOpPersistentStorage(solutionKey); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs index b3015195fd3f3..5f5bc076f5df3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs @@ -23,5 +23,5 @@ public static IChecksummedPersistentStorageService GetOrThrow(IPersistentStorage : Instance; public ValueTask GetStorageAsync(SolutionKey solutionKey, CancellationToken cancellationToken) - => new(NoOpPersistentStorage.GetOrThrow(throwOnFailure: false)); + => new(NoOpPersistentStorage.GetOrThrow(solutionKey, throwOnFailure: false)); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/SolutionKey.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/SolutionKey.cs index 13d48d4dd1aec..41ea22e495640 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/SolutionKey.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/SolutionKey.cs @@ -14,14 +14,10 @@ namespace Microsoft.CodeAnalysis.Storage; /// solution load), but querying the data is still desired. /// [DataContract] -internal readonly struct SolutionKey(SolutionId id, string? filePath) +internal readonly record struct SolutionKey( + [property: DataMember(Order = 0)] SolutionId Id, + [property: DataMember(Order = 1)] string? FilePath) { - [DataMember(Order = 0)] - public readonly SolutionId Id = id; - - [DataMember(Order = 1)] - public readonly string? FilePath = filePath; - public static SolutionKey ToSolutionKey(Solution solution) => ToSolutionKey(solution.SolutionState); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/Status/WorkspaceStatusService.cs b/src/Workspaces/Core/Portable/Workspace/Host/Status/DefaultWorkspaceStatusService.cs similarity index 80% rename from src/Workspaces/Core/Portable/Workspace/Host/Status/WorkspaceStatusService.cs rename to src/Workspaces/Core/Portable/Workspace/Host/Status/DefaultWorkspaceStatusService.cs index bb3d225a923ba..02c72cb3e4183 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/Status/WorkspaceStatusService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/Status/DefaultWorkspaceStatusService.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; @@ -14,14 +12,10 @@ namespace Microsoft.CodeAnalysis.Host; [ExportWorkspaceService(typeof(IWorkspaceStatusService), ServiceLayer.Default), Shared] -internal sealed class WorkspaceStatusService : IWorkspaceStatusService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DefaultWorkspaceStatusService() : IWorkspaceStatusService { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public WorkspaceStatusService() - { - } - event EventHandler IWorkspaceStatusService.StatusChanged { add { } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index f1848b42c8618..d458ba5ca481c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Host; -[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.")] +[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.", error: true)] public interface ITemporaryTextStorage : IDisposable { SourceText ReadText(CancellationToken cancellationToken = default); @@ -21,7 +21,7 @@ public interface ITemporaryTextStorage : IDisposable Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); } -[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.")] +[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.", error: true)] public interface ITemporaryStreamStorage : IDisposable { Stream ReadStream(CancellationToken cancellationToken = default); @@ -29,22 +29,3 @@ public interface ITemporaryStreamStorage : IDisposable void WriteStream(Stream stream, CancellationToken cancellationToken = default); Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken = default); } - -/// -/// TemporaryStorage can be used to read and write text to a temporary storage location. -/// -internal interface ITemporaryTextStorageInternal : IDisposable -{ - SourceText ReadText(CancellationToken cancellationToken = default); - Task ReadTextAsync(CancellationToken cancellationToken = default); - void WriteText(SourceText text, CancellationToken cancellationToken = default); - Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); -} - -internal interface ITemporaryStreamStorageInternal : IDisposable -{ - Stream ReadStream(CancellationToken cancellationToken = default); - Task ReadStreamAsync(CancellationToken cancellationToken = default); - void WriteStream(Stream stream, CancellationToken cancellationToken = default); - Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken = default); -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 694b26ba147b0..c19c7b99a852b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -3,19 +3,56 @@ // 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.Text; namespace Microsoft.CodeAnalysis.Host; -[Obsolete("API is no longer available")] +[Obsolete("API is no longer available", error: true)] public interface ITemporaryStorageService : IWorkspaceService { ITemporaryStreamStorage CreateTemporaryStreamStorage(CancellationToken cancellationToken = default); ITemporaryTextStorage CreateTemporaryTextStorage(CancellationToken cancellationToken = default); } +/// +/// API to allow a client to write data to memory-mapped-file storage. That data can be read back in within the same +/// process using a handle returned from the writing call. The data can optionally be read back in from a different +/// process, using the information contained with the handle's Identifier (see ), but only on systems that support named memory mapped files. Currently, this +/// is any .net on Windows and mono on unix systems. This is not supported on .net core on unix systems (tracked here +/// https://github.com/dotnet/runtime/issues/30878). This is not a problem in practice as cross process sharing is only +/// needed by the VS host, which is windows only. +/// internal interface ITemporaryStorageServiceInternal : IWorkspaceService { - ITemporaryStreamStorageInternal CreateTemporaryStreamStorage(); - ITemporaryTextStorageInternal CreateTemporaryTextStorage(); + /// + /// Write the provided to a new memory-mapped-file. Returns a handle to the data that can + /// be used to identify the data across processes allowing it to be read back in in any process. + /// + /// + /// This type is primarily used to allow dumping metadata to disk. This then allowing them to be read in by mapping + /// their data into types like . It also allows them to be read in by our server + /// process, without having to transmit the data over the wire. + /// Note: The stream provided must support . The stream will also be reset to + /// 0 within this method. The caller does not need to reset the stream + /// itself. + /// + ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); + + /// + /// Write the provided to a new memory-mapped-file. Returns a handle to the data that can + /// be used to identify the data across processes allowing it to be read back in in any process. + /// + /// + /// This type is primarily used to allow dumping source texts to disk. This then allowing them to be read in by + /// mapping their data into types like . It also allows them + /// to be read in by our server process, without having to transmit the data over the wire. + /// + ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken); + + /// "/> + Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService2.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService2.cs deleted file mode 100644 index 9edf4dc0fe7e1..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService2.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Text; -using System.Threading; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.Host; - -/// -/// This service allows you to access temporary storage. -/// -internal interface ITemporaryStorageService2 : ITemporaryStorageServiceInternal -{ - /// - /// Attach to existing with given name. - /// - ITemporaryStreamStorageInternal AttachTemporaryStreamStorage(string storageName, long offset, long size); - - /// - /// Attach to existing with given name. - /// - ITemporaryTextStorageInternal AttachTemporaryTextStorage(string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding); -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs new file mode 100644 index 0000000000000..a278f5a73ab30 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Threading; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is +/// alive, the data should remain in storage and can be readable from any process using the information provided in . Use to write the data to temporary storage and get a handle to it. Use to read the data back in any process. +/// +internal interface ITemporaryStorageStreamHandle +{ + public TemporaryStorageIdentifier Identifier { get; } + + /// + /// Reads the data indicated to by this handle into a stream. This stream can be created in a different process + /// than the one that wrote the data originally. + /// + UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs new file mode 100644 index 0000000000000..d2c1dcccc88b1 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Host; + +internal interface ITemporaryStorageTextHandle +{ + public TemporaryStorageIdentifier Identifier { get; } + + SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); + Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs deleted file mode 100644 index 3d635adbe6c8d..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.CodeAnalysis.Host; - -/// -/// TemporaryStorage can be used to read and write text to a temporary storage location. -/// -internal interface ITemporaryStorageWithName -{ - // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 - // Name shouldn't be nullable. - - /// - /// Get name of the temporary storage - /// - string? Name { get; } - - /// - /// Get offset of the temporary storage - /// - long Offset { get; } - - /// - /// Get size of the temporary storage - /// - long Size { get; } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs deleted file mode 100644 index af0d841bea463..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; - -namespace Microsoft.CodeAnalysis.Host; - -internal static class ITemporaryStreamStorageExtensions -{ - public static void WriteAllLines(this ITemporaryStreamStorageInternal storage, ImmutableArray values) - { - using var stream = SerializableBytes.CreateWritableStream(); - using var writer = new StreamWriter(stream); - - foreach (var value in values) - { - writer.WriteLine(value); - } - - writer.Flush(); - stream.Position = 0; - - storage.WriteStream(stream); - } - - public static ImmutableArray ReadLines(this ITemporaryStreamStorageInternal storage) - { - return EnumerateLines(storage).ToImmutableArray(); - } - - private static IEnumerable EnumerateLines(ITemporaryStreamStorageInternal storage) - { - using var stream = storage.ReadStream(); - using var reader = new StreamReader(stream); - - string line; - while ((line = reader.ReadLine()) != null) - { - yield return line; - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryTextStorageWithName.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryTextStorageWithName.cs deleted file mode 100644 index 918fbdd854b0a..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryTextStorageWithName.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. - -using System.Collections.Immutable; -using System.Text; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.Host; - -/// -/// Represents a which is used to hold data for . -/// -internal interface ITemporaryTextStorageWithName : ITemporaryTextStorageInternal, ITemporaryStorageWithName -{ - /// - /// Gets the value for the property for the - /// represented by this temporary storage. - /// - SourceHashAlgorithm ChecksumAlgorithm { get; } - - /// - /// Gets the value for the property for the - /// represented by this temporary storage. - /// - Encoding? Encoding { get; } - - /// - /// Gets the checksum for the represented by this temporary storage. This is equivalent - /// to calling . - /// - ImmutableArray GetContentHash(); -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs new file mode 100644 index 0000000000000..8ac99018bef6b --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.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 Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Identifier for a stream of data placed in a segment of a memory mapped file. Can be used to identify that segment +/// across processes (where supported), allowing for efficient sharing of data. +/// +/// The name of the segment in the temporary storage. on platforms that don't +/// support cross process sharing of named memory mapped files. +internal sealed record TemporaryStorageIdentifier( + string? Name, long Offset, long Size) +{ + public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) + => new( + reader.ReadString(), + reader.ReadInt64(), + reader.ReadInt64()); + + public void WriteTo(ObjectWriter writer) + { + writer.WriteString(Name); + writer.WriteInt64(Offset); + writer.WriteInt64(Size); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs deleted file mode 100644 index 0f3641b459ca7..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ /dev/null @@ -1,111 +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.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis; - -internal sealed class TrivialTemporaryStorageService : ITemporaryStorageServiceInternal -{ - public static readonly TrivialTemporaryStorageService Instance = new(); - - private TrivialTemporaryStorageService() - { - } - - public ITemporaryStreamStorageInternal CreateTemporaryStreamStorage() - => new StreamStorage(); - - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - => new TextStorage(); - - private sealed class StreamStorage : ITemporaryStreamStorageInternal - { - private MemoryStream? _stream; - - public void Dispose() - { - _stream?.Dispose(); - _stream = null; - } - - public Stream ReadStream(CancellationToken cancellationToken) - { - var stream = _stream ?? throw new InvalidOperationException(); - - // Return a read-only view of the underlying buffer to prevent users from overwriting or directly - // disposing the backing storage. - return new MemoryStream(stream.GetBuffer(), 0, (int)stream.Length, writable: false); - } - - public Task ReadStreamAsync(CancellationToken cancellationToken) - { - return Task.FromResult(ReadStream(cancellationToken)); - } - - public void WriteStream(Stream stream, CancellationToken cancellationToken) - { - var newStream = new MemoryStream(); - stream.CopyTo(newStream); - var existingValue = Interlocked.CompareExchange(ref _stream, newStream, null); - if (existingValue is not null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - } - - public async Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken) - { - var newStream = new MemoryStream(); -#if NETCOREAPP - await stream.CopyToAsync(newStream, cancellationToken).ConfigureAwait(false); -# else - await stream.CopyToAsync(newStream).ConfigureAwait(false); -#endif - var existingValue = Interlocked.CompareExchange(ref _stream, newStream, null); - if (existingValue is not null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - } - } - - private sealed class TextStorage : ITemporaryTextStorageInternal - { - private SourceText? _sourceText; - - public void Dispose() - => _sourceText = null; - - public SourceText ReadText(CancellationToken cancellationToken) - => _sourceText ?? throw new InvalidOperationException(); - - public Task ReadTextAsync(CancellationToken cancellationToken) - => Task.FromResult(ReadText(cancellationToken)); - - public void WriteText(SourceText text, CancellationToken cancellationToken) - { - // This is a trivial implementation, indeed. Note, however, that we retain a strong - // reference to the source text, which defeats the intent of RecoverableTextAndVersion, but - // is appropriate for this trivial implementation. - var existingValue = Interlocked.CompareExchange(ref _sourceText, text, null); - if (existingValue is not null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - } - - public Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default) - { - WriteText(text, cancellationToken); - return Task.CompletedTask; - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs b/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs index c5db31d00e001..288fbddf9fc72 100644 --- a/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs +++ b/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs @@ -37,7 +37,8 @@ internal readonly record struct WorkspaceConfigurationOptions( [property: DataMember(Order = 0)] StorageDatabase CacheStorage = StorageDatabase.SQLite, [property: DataMember(Order = 1)] bool EnableOpeningSourceGeneratedFiles = false, [property: DataMember(Order = 2)] bool DisableRecoverableText = false, - [property: DataMember(Order = 3)] bool ValidateCompilationTrackerStates = + [property: DataMember(Order = 3)] SourceGeneratorExecutionPreference SourceGeneratorExecution = SourceGeneratorExecutionPreference.Automatic, + [property: DataMember(Order = 4)] bool ValidateCompilationTrackerStates = #if DEBUG // We will default this on in DEBUG builds true #else diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IProjectSystemDiagnosticSource.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IProjectSystemDiagnosticSource.cs index e44aebed5200d..272c88bc422f7 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IProjectSystemDiagnosticSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IProjectSystemDiagnosticSource.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. -using System.Collections.Generic; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem; @@ -11,9 +9,5 @@ namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem; // TODO: see if we can get rid of this interface by appropriately rewriting HostDiagnosticUpdateSource to live at the workspaces layer. internal interface IProjectSystemDiagnosticSource { - void ClearAllDiagnosticsForProject(ProjectId projectId); - void ClearAnalyzerReferenceDiagnostics(AnalyzerFileReference fileReference, string language, ProjectId projectId); - void ClearDiagnosticsForProject(ProjectId projectId, object key); DiagnosticData CreateAnalyzerLoadFailureDiagnostic(AnalyzerLoadFailureEventArgs e, string fullPath, ProjectId projectId, string language); - void UpdateDiagnosticsForProject(ProjectId projectId, object key, IEnumerable items); } diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs index 929d5f854e0ae..0f6ed5627c13a 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs @@ -425,7 +425,7 @@ await _project._projectSystemProjectFactory.ApplyBatchChangeToWorkspaceAsync(sol solutionChanges.UpdateSolutionForDocumentAction( _documentTextLoaderChangedAction(solutionChanges.Solution, documentId, textLoader), _documentChangedWorkspaceKind, - SpecializedCollections.SingletonEnumerable(documentId)); + [documentId]); } } }).ConfigureAwait(false); @@ -557,7 +557,7 @@ internal void UpdateSolutionForBatch( ClearAndZeroCapacity(_documentsAddedInBatch); // Document removing... - solutionChanges.UpdateSolutionForRemovedDocumentAction(removeDocuments(solutionChanges.Solution, _documentsRemovedInBatch.ToImmutableArray()), + solutionChanges.UpdateSolutionForRemovedDocumentAction(removeDocuments(solutionChanges.Solution, [.. _documentsRemovedInBatch]), removeDocumentChangeKind, _documentsRemovedInBatch); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index fd6599b2c1565..4f387f594bed4 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -191,21 +191,30 @@ internal ProjectSystemProject( _filePath = filePath; _parseOptions = parseOptions; - var fileExtensionToWatch = language switch { LanguageNames.CSharp => ".cs", LanguageNames.VisualBasic => ".vb", _ => null }; + var watchedDirectories = GetWatchedDirectories(language, filePath); + _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(watchedDirectories); + _documentFileChangeContext.FileChanged += DocumentFileChangeContext_FileChanged; - if (filePath != null && fileExtensionToWatch != null) + static WatchedDirectory[] GetWatchedDirectories(string? language, string? filePath) { - // Since we have a project directory, we'll just watch all the files under that path; that'll avoid extra overhead of - // having to add explicit file watches everywhere. - var projectDirectoryToWatch = new WatchedDirectory(Path.GetDirectoryName(filePath)!, fileExtensionToWatch); - _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(projectDirectoryToWatch); - } - else - { - _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(); - } + if (filePath is null) + { + return []; + } - _documentFileChangeContext.FileChanged += DocumentFileChangeContext_FileChanged; + var rootPath = Path.GetDirectoryName(filePath); + if (rootPath is null) + { + return []; + } + + return language switch + { + LanguageNames.VisualBasic => [new(rootPath, ".vb")], + LanguageNames.CSharp => [new(rootPath, ".cs"), new(rootPath, ".razor"), new(rootPath, ".cshtml")], + _ => [] + }; + } } private void ChangeProjectProperty(ref T field, T newValue, Func updateSolution, bool logThrowAwayTelemetry = false) @@ -533,35 +542,27 @@ await _projectSystemProjectFactory.ApplyBatchChangeToWorkspaceMaybeAsync(useAsyn solutionChanges, documentFileNamesAdded, documentsToOpen, - (s, documents) => s.AddDocuments(documents), + static (s, documents) => s.AddDocuments(documents), WorkspaceChangeKind.DocumentAdded, - (s, ids) => s.RemoveDocuments(ids), + static (s, ids) => s.RemoveDocuments(ids), WorkspaceChangeKind.DocumentRemoved); _additionalFiles.UpdateSolutionForBatch( solutionChanges, documentFileNamesAdded, additionalDocumentsToOpen, - (s, documents) => - { - foreach (var document in documents) - { - s = s.AddAdditionalDocument(document); - } - - return s; - }, + static (s, documents) => s.AddAdditionalDocuments(documents), WorkspaceChangeKind.AdditionalDocumentAdded, - (s, ids) => s.RemoveAdditionalDocuments(ids), + static (s, ids) => s.RemoveAdditionalDocuments(ids), WorkspaceChangeKind.AdditionalDocumentRemoved); _analyzerConfigFiles.UpdateSolutionForBatch( solutionChanges, documentFileNamesAdded, analyzerConfigDocumentsToOpen, - (s, documents) => s.AddAnalyzerConfigDocuments(documents), + static (s, documents) => s.AddAnalyzerConfigDocuments(documents), WorkspaceChangeKind.AnalyzerConfigDocumentAdded, - (s, ids) => s.RemoveAnalyzerConfigDocuments(ids), + static (s, ids) => s.RemoveAnalyzerConfigDocuments(ids), WorkspaceChangeKind.AnalyzerConfigDocumentRemoved); // Metadata reference removing. Do this before adding in case this removes a project reference that diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs index baec14c7723c1..c97750feb434d 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.ProjectSystem; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -133,8 +134,9 @@ await ApplyChangeToWorkspaceAsync(w => analyzerReferences: w.CurrentSolution.AnalyzerReferences).WithTelemetryId(SolutionTelemetryId); var newSolution = w.CreateSolution(solutionInfo); - foreach (var project in solutionInfo.Projects) - newSolution = newSolution.AddProject(project); + using var _ = ArrayBuilder.GetInstance(out var projectInfos); + projectInfos.AddRange(solutionInfo.Projects); + newSolution = newSolution.AddProjects(projectInfos); return newSolution; } diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs index 4d6a793c21a02..53e50dee4cf74 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Immutable; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 11be0ef802128..9c2059d1006c3 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -3,8 +3,10 @@ // 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.Threading; using Microsoft.CodeAnalysis.Host; using Roslyn.Utilities; @@ -36,7 +38,7 @@ internal class ProjectSystemProjectOptionsProcessor : IDisposable /// (especially in cases with many references). /// /// Note: this will be null in the case that the command line is an empty array. - private ITemporaryStreamStorageInternal? _commandLineStorage; + private ITemporaryStorageStreamHandle? _commandLineStorageHandle; private CommandLineArguments _commandLineArgumentsForCommandLine; private string? _explicitRuleSetFilePath; @@ -71,12 +73,17 @@ private bool ReparseCommandLineIfChanged_NoLock(ImmutableArray arguments // Dispose the existing stored command-line and then persist the new one so we can // recover it later. Only bother persisting things if we have a non-empty string. - _commandLineStorage?.Dispose(); - _commandLineStorage = null; + _commandLineStorageHandle = null; if (!arguments.IsEmpty) { - _commandLineStorage = _temporaryStorageService.CreateTemporaryStreamStorage(); - _commandLineStorage.WriteAllLines(arguments); + using var stream = SerializableBytes.CreateWritableStream(); + using var writer = new StreamWriter(stream); + + foreach (var value in arguments) + writer.WriteLine(value); + + writer.Flush(); + _commandLineStorageHandle = _temporaryStorageService.WriteToTemporaryStorage(stream, CancellationToken.None); } ReparseCommandLine_NoLock(arguments); @@ -237,12 +244,24 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) // effective values was potentially done by the act of parsing the command line. Even though the command line didn't change textually, // the effective result did. Then we call UpdateProjectOptions_NoLock to reapply any values; that will also re-acquire the new ruleset // includes in the IDE so we can be watching for changes again. - var commandLine = _commandLineStorage == null ? ImmutableArray.Empty : _commandLineStorage.ReadLines(); + var commandLine = _commandLineStorageHandle == null + ? ImmutableArray.Empty + : EnumerateLines(_commandLineStorageHandle).ToImmutableArray(); DisposeOfRuleSetFile_NoLock(); ReparseCommandLine_NoLock(commandLine); UpdateProjectOptions_NoLock(); } + + static IEnumerable EnumerateLines( + ITemporaryStorageStreamHandle storageHandle) + { + using var stream = storageHandle.ReadFromTemporaryStorage(CancellationToken.None); + using var reader = new StreamReader(stream); + + while (reader.ReadLine() is string line) + yield return line; + } } /// @@ -276,7 +295,6 @@ public void Dispose() lock (_gate) { DisposeOfRuleSetFile_NoLock(); - _commandLineStorage?.Dispose(); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/VisualStudioAnalyzer.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/VisualStudioAnalyzer.cs index 2d6003d58fa7a..93437ffffa929 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/VisualStudioAnalyzer.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/VisualStudioAnalyzer.cs @@ -51,7 +51,6 @@ private void OnAnalyzerLoadError(object? sender, AnalyzerLoadFailureEventArgs e) lock (_gate) { _analyzerLoadErrors = _analyzerLoadErrors.Add(data); - projectSystemDiagnosticSource.UpdateDiagnosticsForProject(projectId, this, _analyzerLoadErrors); } } @@ -62,13 +61,6 @@ public void Dispose() if (reference is AnalyzerFileReference fileReference) { fileReference.AnalyzerLoadFailed -= OnAnalyzerLoadError; - - if (!loadErrors.IsEmpty) - { - projectSystemDiagnosticSource.ClearDiagnosticsForProject(projectId, this); - } - - projectSystemDiagnosticSource.ClearAnalyzerReferenceDiagnostics(fileReference, language, projectId); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs deleted file mode 100644 index 7f035466d93dd..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.Serialization; - -namespace Microsoft.CodeAnalysis.Serialization; - -/// -/// Optional information passed with an asset synchronization request to allow the request to be scoped down to a -/// particular or . -/// -[DataContract] -internal readonly struct AssetHint -{ - public static readonly AssetHint None = default; - - [DataMember(Order = 0)] - public readonly ProjectId? ProjectId; - [DataMember(Order = 1)] - public readonly DocumentId? DocumentId; - - private AssetHint(ProjectId? projectId, DocumentId? documentId) - { - ProjectId = projectId; - DocumentId = documentId; - } - - public static implicit operator AssetHint(ProjectId projectId) => new(projectId, documentId: null); - public static implicit operator AssetHint(DocumentId documentId) => new(documentId.ProjectId, documentId); -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs new file mode 100644 index 0000000000000..85b8d73952855 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more 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 Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Serialization; + +/// +/// Required information passed with an asset synchronization request to tell the host where to scope the request to. In +/// particular, this is often used to scope to a particular or to avoid +/// having to search the entire solution. +/// +[DataContract] +internal readonly struct AssetPath +{ + /// + /// Special instance, allowed only in tests/debug-asserts, that can do a full lookup across the entire checksum + /// tree. Should not be used in normal release-mode product code. + /// + public static readonly AssetPath FullLookupForTesting = AssetPathKind.SolutionCompilationState | AssetPathKind.SolutionState | AssetPathKind.Projects | AssetPathKind.Documents; + + [DataMember(Order = 0)] + private readonly AssetPathKind _kind; + + /// + /// If not null, the search should only descend into the single project with this id. + /// + [DataMember(Order = 1)] + public readonly ProjectId? ProjectId; + + /// + /// If not null, the search should only descend into the single document with this id. + /// + [DataMember(Order = 2)] + public readonly DocumentId? DocumentId; + + public AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? documentId = null) + { + _kind = kind; + ProjectId = projectId; + DocumentId = documentId; + } + + public AssetPath(AssetPathKind kind, ProjectId? projectId) + : this(kind, projectId, documentId: null) + { + } + + public AssetPath(AssetPathKind kind, DocumentId? documentId) + : this(kind, documentId?.ProjectId, documentId) + { + } + + public bool IncludeSolutionCompilationState => (_kind & AssetPathKind.SolutionCompilationState) != 0; + public bool IncludeSolutionState => (_kind & AssetPathKind.SolutionState) != 0; + public bool IncludeProjects => (_kind & AssetPathKind.Projects) != 0; + public bool IncludeDocuments => (_kind & AssetPathKind.Documents) != 0; + + public bool IncludeSolutionCompilationStateChecksums => (_kind & AssetPathKind.SolutionCompilationStateChecksums) != 0; + public bool IncludeSolutionSourceGeneratorExecutionVersionMap => (_kind & AssetPathKind.SolutionSourceGeneratorExecutionVersionMap) != 0; + public bool IncludeSolutionFrozenSourceGeneratedDocumentIdentities => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentIdentities) != 0; + public bool IncludeSolutionFrozenSourceGeneratedDocumentText => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentText) != 0; + + public bool IncludeSolutionStateChecksums => (_kind & AssetPathKind.SolutionStateChecksums) != 0; + public bool IncludeSolutionAttributes => (_kind & AssetPathKind.SolutionAttributes) != 0; + public bool IncludeSolutionAnalyzerReferences => (_kind & AssetPathKind.SolutionAnalyzerReferences) != 0; + + public bool IncludeProjectStateChecksums => (_kind & AssetPathKind.ProjectStateChecksums) != 0; + public bool IncludeProjectAttributes => (_kind & AssetPathKind.ProjectAttributes) != 0; + public bool IncludeProjectCompilationOptions => (_kind & AssetPathKind.ProjectCompilationOptions) != 0; + public bool IncludeProjectParseOptions => (_kind & AssetPathKind.ProjectParseOptions) != 0; + public bool IncludeProjectProjectReferences => (_kind & AssetPathKind.ProjectProjectReferences) != 0; + public bool IncludeProjectMetadataReferences => (_kind & AssetPathKind.ProjectMetadataReferences) != 0; + public bool IncludeProjectAnalyzerReferences => (_kind & AssetPathKind.ProjectAnalyzerReferences) != 0; + + public bool IncludeDocumentAttributes => (_kind & AssetPathKind.DocumentAttributes) != 0; + public bool IncludeDocumentText => (_kind & AssetPathKind.DocumentText) != 0; + + public static implicit operator AssetPath(AssetPathKind kind) => new(kind); + + /// + /// Searches only for information about this project. + /// + public static implicit operator AssetPath(ProjectId projectId) => new(AssetPathKind.Projects, projectId); + + /// + /// Searches only for information about this document. + /// + public static implicit operator AssetPath(DocumentId documentId) => new(AssetPathKind.Documents, documentId); + + /// + /// Searches the requested project, and all documents underneath it. Used only in tests. + /// + /// + /// + public static AssetPath SolutionAndProjectForTesting(ProjectId projectId) + => new(AssetPathKind.SolutionCompilationState | AssetPathKind.SolutionState | AssetPathKind.Projects, projectId); + + /// + /// Searches all documents within the specified project. + /// + /// + /// + public static AssetPath DocumentsInProject(ProjectId projectId) + => new(AssetPathKind.Documents, projectId); +} + +[Flags] +internal enum AssetPathKind +{ + SolutionCompilationStateChecksums = 1 << 0, + SolutionSourceGeneratorExecutionVersionMap = 1 << 2, + SolutionFrozenSourceGeneratedDocumentIdentities = 1 << 3, + SolutionFrozenSourceGeneratedDocumentText = 1 << 4, + + // Keep a gap so we can easily add more solution compilation state kinds + SolutionStateChecksums = 1 << 10, + SolutionAttributes = 1 << 11, + SolutionAnalyzerReferences = 1 << 12, + + // Keep a gap so we can easily add more solution kinds + ProjectStateChecksums = 1 << 15, + ProjectAttributes = 1 << 16, + ProjectCompilationOptions = 1 << 17, + ProjectParseOptions = 1 << 18, + ProjectProjectReferences = 1 << 19, + ProjectMetadataReferences = 1 << 20, + ProjectAnalyzerReferences = 1 << 21, + + // Keep a gap so we can easily add more project kinds + DocumentAttributes = 1 << 25, + DocumentText = 1 << 26, + + /// + /// Search solution-compilation-state level information. + /// + SolutionCompilationState = SolutionCompilationStateChecksums | SolutionSourceGeneratorExecutionVersionMap | SolutionFrozenSourceGeneratedDocumentIdentities | SolutionFrozenSourceGeneratedDocumentText, + + /// + /// Search solution-state level information. + /// + SolutionState = SolutionStateChecksums | SolutionAttributes | SolutionAnalyzerReferences, + + /// + /// Search projects for results. All project-level information will be searched. + /// + Projects = ProjectStateChecksums | ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, + + /// + /// Search documents for results. + /// + Documents = DocumentAttributes | DocumentText, +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs index 0a5b669b9ae69..f425a52edcacb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs @@ -5,11 +5,11 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO.Pipelines; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; -using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -89,6 +89,13 @@ public void WriteTo(Span span) Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(span), this); } + public void WriteTo(PipeWriter pipeWriter) + { + var span = pipeWriter.GetSpan(HashSize); + this.WriteTo(span); + pipeWriter.Advance(HashSize); + } + public static Checksum ReadFrom(ObjectReader reader) => new(reader.ReadInt64(), reader.ReadInt64()); @@ -98,9 +105,6 @@ public static Checksum ReadFrom(ObjectReader reader) public static Func, string> GetChecksumsLogInfo { get; } = checksums => string.Join("|", checksums.Select(c => c.ToString())); - public static Func GetProjectChecksumsLogInfo { get; } - = checksums => checksums.Checksum.ToString(); - // Explicitly implement this method as default jit for records on netfx doesn't properly devirtualize the // standard calls to EqualityComparer.Default.Equals public bool Equals(Checksum other) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index 80e04bb9ed8e8..0779fd4d20e41 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -54,20 +54,22 @@ public void AddAllTo(HashSet checksums) } [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1333566", AllowGenericEnumeration = false)] - internal static async Task FindAsync( + internal static async Task FindAsync( + AssetPath assetPath, TextDocumentStates documentStates, - DocumentId? hintDocument, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) where TState : TextDocumentState { + var hintDocument = assetPath.DocumentId; if (hintDocument != null) { var state = documentStates.GetState(hintDocument); if (state != null) { Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(state, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } else @@ -80,16 +82,17 @@ internal static async Task FindAsync( Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(state, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } } - internal static void Find( + internal static void Find( IReadOnlyList values, ChecksumCollection checksums, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) where T : class { Contract.ThrowIfFalse(values.Count == checksums.Children.Length); @@ -102,7 +105,7 @@ internal static void Find( var checksum = checksums.Children[i]; if (searchingChecksumsLeft.Remove(checksum)) - result[checksum] = values[i]; + onAssetFound(checksum, values[i], arg); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs index d269819d14acf..ead02e2f244d2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs @@ -74,6 +74,9 @@ public static Checksum Create(Checksum checksum1, Checksum checksum2) public static Checksum Create(Checksum checksum1, Checksum checksum2, Checksum checksum3) => Create(stackalloc[] { checksum1, checksum2, checksum3 }); + public static Checksum Create(Checksum checksum1, Checksum checksum2, Checksum checksum3, Checksum checksum4) + => Create(stackalloc[] { checksum1, checksum2, checksum3, checksum4 }); + public static Checksum Create(ReadOnlySpan hashes) { Span destination = stackalloc byte[XXHash128SizeBytes]; @@ -82,71 +85,32 @@ public static Checksum Create(ReadOnlySpan hashes) } public static Checksum Create(ArrayBuilder checksums) - { - using var stream = SerializableBytes.CreateWritableStream(); - - using (var writer = new ObjectWriter(stream, leaveOpen: true)) + => Create(checksums, static (checksums, writer) => { foreach (var checksum in checksums) checksum.WriteTo(writer); - } - - stream.Position = 0; - return Create(stream); - } + }); public static Checksum Create(ImmutableArray checksums) - { - using var stream = SerializableBytes.CreateWritableStream(); - - using (var writer = new ObjectWriter(stream, leaveOpen: true)) + => Create(checksums, static (checksums, writer) => { foreach (var checksum in checksums) checksum.WriteTo(writer); - } - - stream.Position = 0; - return Create(stream); - } + }); public static Checksum Create(ImmutableArray bytes) - { - using var stream = SerializableBytes.CreateWritableStream(); - - using (var writer = new ObjectWriter(stream, leaveOpen: true)) + => Create(bytes, static (bytes, writer) => { - for (var i = 0; i < bytes.Length; i++) - writer.WriteByte(bytes[i]); - } - - stream.Position = 0; - return Create(stream); - } - - public static Checksum Create(T value, ISerializerService serializer) - { - using var stream = SerializableBytes.CreateWritableStream(); - using var context = new SolutionReplicationContext(); - - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) - { - serializer.Serialize(value!, objectWriter, context, CancellationToken.None); - } - - stream.Position = 0; - return Create(stream); - } - - public static Checksum Create(ParseOptions value, ISerializerService serializer) - { - using var stream = SerializableBytes.CreateWritableStream(); - - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) - { - serializer.SerializeParseOptions(value, objectWriter); - } - - stream.Position = 0; - return Create(stream); - } + foreach (var b in bytes) + writer.WriteByte(b); + }); + + public static Checksum Create(T value, ISerializerService serializer, CancellationToken cancellationToken) + => Create( + (value, serializer, cancellationToken), + static (tuple, writer) => + { + var (value, serializer, cancellationToken) = tuple; + serializer.Serialize(value!, writer, cancellationToken); + }); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs index f144ede095678..23b49cca4c0dc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.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; +using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -10,36 +10,15 @@ namespace Microsoft.CodeAnalysis.Serialization; /// -/// A paired list of IDs (either s or s), and the checksums for their -/// corresponding s or s). +/// A paired list of s, and the checksums for their corresponding 's . /// -internal readonly struct ChecksumsAndIds +internal readonly struct ProjectChecksumsAndIds { public readonly ChecksumCollection Checksums; - public readonly ImmutableArray Ids; + public readonly ImmutableArray Ids; - private static readonly Func s_readId; - private static readonly Action s_writeTo; - - static ChecksumsAndIds() - { - if (typeof(TId) == typeof(ProjectId)) - { - s_readId = reader => (TId)(object)ProjectId.ReadFrom(reader); - s_writeTo = (writer, id) => ((ProjectId)(object)id!).WriteTo(writer); - } - else if (typeof(TId) == typeof(DocumentId)) - { - s_readId = reader => (TId)(object)DocumentId.ReadFrom(reader); - s_writeTo = (writer, id) => ((DocumentId)(object)id!).WriteTo(writer); - } - else - { - throw ExceptionUtilities.Unreachable(); - } - } - - public ChecksumsAndIds(ChecksumCollection checksums, ImmutableArray ids) + public ProjectChecksumsAndIds(ChecksumCollection checksums, ImmutableArray ids) { Contract.ThrowIfTrue(ids.Length != checksums.Children.Length); @@ -53,28 +32,93 @@ public ChecksumsAndIds(ChecksumCollection checksums, ImmutableArray ids) public void WriteTo(ObjectWriter writer) { this.Checksums.WriteTo(writer); - writer.WriteArray(this.Ids, s_writeTo); + writer.WriteArray(this.Ids, static (writer, p) => p.WriteTo(writer)); } - public static ChecksumsAndIds ReadFrom(ObjectReader reader) + public static ProjectChecksumsAndIds ReadFrom(ObjectReader reader) { return new( ChecksumCollection.ReadFrom(reader), - reader.ReadArray(s_readId)); + reader.ReadArray(static reader => ProjectId.ReadFrom(reader))); } public Enumerator GetEnumerator() => new(this); - public struct Enumerator(ChecksumsAndIds checksumsAndIds) + public struct Enumerator(ProjectChecksumsAndIds checksumsAndIds) { - private readonly ChecksumsAndIds _checksumsAndIds = checksumsAndIds; + private readonly ProjectChecksumsAndIds _checksumsAndIds = checksumsAndIds; private int _index = -1; public bool MoveNext() => ++_index < _checksumsAndIds.Length; - public (Checksum checksum, TId id) Current + public readonly (Checksum checksum, ProjectId id) Current => (_checksumsAndIds.Checksums.Children[_index], _checksumsAndIds.Ids[_index]); } } + +/// +/// A paired list of s, and the checksums for their corresponding 's and checksums. +/// +internal readonly struct DocumentChecksumsAndIds +{ + public readonly Checksum Checksum; + public readonly ChecksumCollection AttributeChecksums; + public readonly ChecksumCollection TextChecksums; + public readonly ImmutableArray Ids; + + public DocumentChecksumsAndIds(ChecksumCollection attributeChecksums, ChecksumCollection textChecksums, ImmutableArray ids) + { + Contract.ThrowIfTrue(ids.Length != attributeChecksums.Children.Length); + Contract.ThrowIfTrue(ids.Length != textChecksums.Children.Length); + + AttributeChecksums = attributeChecksums; + TextChecksums = textChecksums; + Ids = ids; + + Checksum = Checksum.Create(attributeChecksums.Checksum, textChecksums.Checksum); + } + + public int Length => Ids.Length; + + public void WriteTo(ObjectWriter writer) + { + this.AttributeChecksums.WriteTo(writer); + this.TextChecksums.WriteTo(writer); + writer.WriteArray(this.Ids, static (writer, id) => id.WriteTo(writer)); + } + + public static DocumentChecksumsAndIds ReadFrom(ObjectReader reader) + { + return new( + ChecksumCollection.ReadFrom(reader), + ChecksumCollection.ReadFrom(reader), + reader.ReadArray(static reader => DocumentId.ReadFrom(reader))); + } + + public void AddAllTo(HashSet checksums) + { + this.AttributeChecksums.AddAllTo(checksums); + this.TextChecksums.AddAllTo(checksums); + } + + public Enumerator GetEnumerator() + => new(this); + + public struct Enumerator(DocumentChecksumsAndIds checksumsAndIds) + { + private readonly DocumentChecksumsAndIds _checksumsAndIds = checksumsAndIds; + private int _index = -1; + + public bool MoveNext() + => ++_index < _checksumsAndIds.Length; + + public readonly (Checksum attributeChecksum, Checksum textChecksum, DocumentId id) Current + => (_checksumsAndIds.AttributeChecksums.Children[_index], + _checksumsAndIds.TextChecksums.Children[_index], + _checksumsAndIds.Ids[_index]); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs index 32714a34a1c44..3313ec6f1c845 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs @@ -19,6 +19,12 @@ internal sealed class ConstantTextAndVersionSource(TextAndVersion value) : IText public bool CanReloadText => false; + /// + /// Not built from a text loader. + /// + public TextLoader? TextLoader + => null; + public TextAndVersion GetValue(LoadTextOptions options, CancellationToken cancellationToken) => _value; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 43f4eca13e51e..379fabf46b317 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -326,60 +326,56 @@ internal async ValueTask GetRequiredNullableDisabledSemanticModel /// private async Task GetSemanticModelHelperAsync(bool disableNullableAnalysis, CancellationToken cancellationToken) { - try - { - if (!this.SupportsSemanticModel) - { - return null; - } + if (!this.SupportsSemanticModel) + return null; + + var semanticModel = await GetSemanticModelWorkerAsync().ConfigureAwait(false); + this.Project.Solution.OnSemanticModelObtained(this.Id, semanticModel); + return semanticModel; - SemanticModel? semanticModel; - if (disableNullableAnalysis) + async Task GetSemanticModelWorkerAsync() + { + try { - if (this.TryGetNullableDisabledSemanticModel(out semanticModel)) + if (disableNullableAnalysis) { - return semanticModel; + if (this.TryGetNullableDisabledSemanticModel(out var semanticModel)) + return semanticModel; } - } - else - { - if (this.TryGetSemanticModel(out semanticModel)) + else { - return semanticModel; + if (this.TryGetSemanticModel(out var semanticModel)) + return semanticModel; } - } - var syntaxTree = await this.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var compilation = await this.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var syntaxTree = await this.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var compilation = await this.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); #pragma warning disable RSEXPERIMENTAL001 // sym-shipped usage of experimental API - var result = compilation.GetSemanticModel(syntaxTree, disableNullableAnalysis ? SemanticModelOptions.DisableNullableAnalysis : SemanticModelOptions.None); + var result = compilation.GetSemanticModel(syntaxTree, disableNullableAnalysis ? SemanticModelOptions.DisableNullableAnalysis : SemanticModelOptions.None); #pragma warning restore RSEXPERIMENTAL001 - Contract.ThrowIfNull(result); - var original = Interlocked.CompareExchange(ref disableNullableAnalysis ? ref _nullableDisabledModel : ref _model, new WeakReference(result), null); + Contract.ThrowIfNull(result); + var original = Interlocked.CompareExchange(ref disableNullableAnalysis ? ref _nullableDisabledModel : ref _model, new WeakReference(result), null); - // okay, it is first time. - if (original == null) - { - return result; - } + // okay, it is first time. + if (original == null) + return result; - // It looks like someone has set it. Try to reuse same semantic model, or assign the new model if that - // fails. The lock is required since there is no compare-and-set primitive for WeakReference. - lock (original) - { - if (original.TryGetTarget(out semanticModel)) + // It looks like someone has set it. Try to reuse same semantic model, or assign the new model if that + // fails. The lock is required since there is no compare-and-set primitive for WeakReference. + lock (original) { - return semanticModel; - } + if (original.TryGetTarget(out var semanticModel)) + return semanticModel; - original.SetTarget(result); - return result; + original.SetTarget(result); + return result; + } + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) + { + throw ExceptionUtilities.Unreachable(); } - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) - { - throw ExceptionUtilities.Unreachable(); } } @@ -433,11 +429,9 @@ public async Task> GetTextChangesAsync(Document oldDocum { using (Logger.LogBlock(FunctionId.Workspace_Document_GetTextChanges, this.Name, cancellationToken)) { + // no changes if (oldDocument == this) - { - // no changes - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (this.Id != oldDocument.Id) { @@ -448,9 +442,7 @@ public async Task> GetTextChangesAsync(Document oldDocum if (this.TryGetText(out var text) && oldDocument.TryGetText(out var oldText)) { if (text == oldText) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var container = text.Container; if (container != null) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 9c92c71478ee4..8e7daaa323d16 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -31,7 +31,7 @@ internal partial class DocumentState : TextDocumentState private readonly ParseOptions? _options; // null if the document doesn't support syntax trees: - private readonly AsyncLazy? _treeSource; + private readonly ITreeAndVersionSource? _treeSource; protected DocumentState( LanguageServices languageServices, @@ -40,7 +40,7 @@ protected DocumentState( ParseOptions? options, ITextAndVersionSource textSource, LoadTextOptions loadTextOptions, - AsyncLazy? treeSource) + ITreeAndVersionSource? treeSource) : base(languageServices.SolutionServices, documentServiceProvider, attributes, textSource, loadTextOptions) { Contract.ThrowIfFalse(_options is null == _treeSource is null); @@ -79,7 +79,7 @@ public DocumentState( } } - public AsyncLazy? TreeSource => _treeSource; + public ITreeAndVersionSource? TreeSource => _treeSource; [MemberNotNullWhen(true, nameof(_treeSource))] [MemberNotNullWhen(true, nameof(TreeSource))] @@ -97,7 +97,7 @@ public SourceCodeKind SourceCodeKind public bool IsGenerated => Attributes.IsGenerated; - protected static AsyncLazy CreateLazyFullyParsedTree( + protected static ITreeAndVersionSource CreateLazyFullyParsedTree( ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, string? filePath, @@ -105,7 +105,7 @@ protected static AsyncLazy CreateLazyFullyParsedTree( LanguageServices languageServices, PreservationMode mode = PreservationMode.PreserveValue) { - return AsyncLazy.Create( + return SimpleTreeAndVersionSource.Create( static (arg, c) => FullyParseTreeAsync(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), static (arg, c) => FullyParseTree(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), arg: (newTextSource, loadTextOptions, filePath, options, languageServices, mode)); @@ -163,19 +163,19 @@ private static TreeAndVersion CreateTreeAndVersion( return new TreeAndVersion(tree, textAndVersion.Version); } - private static AsyncLazy CreateLazyIncrementallyParsedTree( - AsyncLazy oldTreeSource, + private static ITreeAndVersionSource CreateLazyIncrementallyParsedTree( + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions) { - return AsyncLazy.Create( + return SimpleTreeAndVersionSource.Create( static (arg, c) => IncrementallyParseTreeAsync(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), static (arg, c) => IncrementallyParseTree(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), arg: (oldTreeSource, newTextSource, loadTextOptions)); } private static async Task IncrementallyParseTreeAsync( - AsyncLazy oldTreeSource, + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, CancellationToken cancellationToken) @@ -197,7 +197,7 @@ private static async Task IncrementallyParseTreeAsync( } private static TreeAndVersion IncrementallyParseTree( - AsyncLazy oldTreeSource, + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, CancellationToken cancellationToken) @@ -346,7 +346,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso throw new InvalidOperationException(); } - AsyncLazy? newTreeSource = null; + ITreeAndVersionSource? newTreeSource = null; // Optimization: if we are only changing preprocessor directives, and we've already parsed the existing tree // and it didn't have any, we can avoid a reparse since the tree will be parsed the same. @@ -368,7 +368,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso } if (newTree is not null) - newTreeSource = AsyncLazy.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); + newTreeSource = SimpleTreeAndVersionSource.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); } // If we weren't able to reuse in a smart way, just reparse @@ -457,7 +457,7 @@ public DocumentState UpdateFilePath(string? filePath) protected override TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental) { - AsyncLazy? newTreeSource; + ITreeAndVersionSource? newTreeSource; if (!SupportsSyntaxTree) { @@ -528,7 +528,7 @@ internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) _options, textSource: text, LoadTextOptions, - treeSource: AsyncLazy.Create(treeAndVersion)); + treeSource: SimpleTreeAndVersionSource.Create(treeAndVersion)); // use static method so we don't capture references to this static (ITextAndVersionSource, TreeAndVersion) CreateTreeWithLazyText( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 9353d1f140a87..8c481c1b4a3be 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -13,6 +13,29 @@ namespace Microsoft.CodeAnalysis; internal partial class DocumentState { + /// + /// when we're linked to another file (a 'sibling') and will attempt to reuse + /// that sibling's tree as our own. Note: we won't know if we can actually use the contents of that sibling file + /// until we actually go and realize it, as it may contains constructs (like pp-directives) that prevent use. In + /// that case, we'll fall back to a normal incremental parse between our original and the latest text contents of our sibling's file. + /// + private sealed class LinkedFileReuseTreeAndVersionSource( + ITreeAndVersionSource originalTreeSource, + AsyncLazy lazyComputation) : ITreeAndVersionSource + { + public readonly ITreeAndVersionSource OriginalTreeSource = originalTreeSource; + + public Task GetValueAsync(CancellationToken cancellationToken) + => lazyComputation.GetValueAsync(cancellationToken); + + public TreeAndVersion GetValue(CancellationToken cancellationToken) + => lazyComputation.GetValue(cancellationToken); + + public bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value) + => lazyComputation.TryGetValue(out value); + } + /// /// Returns a new instance of this document state that points to as the /// text contents of the document, and which will produce a syntax tree that reuses from public DocumentState UpdateTextAndTreeContents( ITextAndVersionSource siblingTextSource, - AsyncLazy? siblingTreeSource, + ITreeAndVersionSource? siblingTreeSource, bool forceEvenIfTreesWouldDiffer) { if (!SupportsSyntaxTree) @@ -38,50 +61,52 @@ public DocumentState UpdateTextAndTreeContents( Contract.ThrowIfNull(siblingTreeSource); + // We don't want to point at a long chain of transformations as our sibling files change, deferring to each next + // link of the chain to potentially do the work (or potentially failing out). So, if we're about to do this, + // instead return our original tree-source so that in the case we are unable to use the sibling file's root, we + // can do a single step incremental parse between our original tree and the final sibling text. + // + // We only need to look one deep here as we'll pull that tree source forward to our level. If another link is + // later added to us, it will do the same thing. + var originalTreeSource = this.TreeSource; + if (originalTreeSource is LinkedFileReuseTreeAndVersionSource linkedFileTreeAndVersionSource) + originalTreeSource = linkedFileTreeAndVersionSource.OriginalTreeSource; + // Always pass along the sibling text. We will always be in sync with that. - // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as - // much memory as possible with linked files. However, we can't point at that source directly. If we did, - // we'd produce the *exact* same tree-reference as another file. That would be bad as it would break the - // invariant that each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers - // to the provided source, gets the tree from it, and then wraps its root in a new tree for us. + // Defer to static helper to make sure we don't accidentally capture anything else we don't want off of 'this' + // (like "this.TreeSource"). + return UpdateTextAndTreeContentsWorker( + this.Attributes, this.LanguageServices, this.Services, this.LoadTextOptions, this.ParseOptions, + originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer); + } - // copy data from this entity, and pass to static helper, so we don't keep this green node alive. + private static DocumentState UpdateTextAndTreeContentsWorker( + DocumentInfo.DocumentAttributes attributes, + LanguageServices languageServices, + IDocumentServiceProvider services, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + ITreeAndVersionSource originalTreeSource, + ITextAndVersionSource siblingTextSource, + ITreeAndVersionSource siblingTreeSource, + bool forceEvenIfTreesWouldDiffer) + { + // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as much + // memory as possible with linked files. However, we can't point at that source directly. If we did, we'd + // produce the *exact* same tree-reference as another file. That would be bad as it would break the invariant + // that each document gets a unique SyntaxTree. So, instead, we produce a tree-source that defers to the + // provided source, gets the tree from it, and then wraps its root in a new tree for us. - var filePath = this.Attributes.SyntaxTreeFilePath; - var languageServices = this.LanguageServices; - var loadTextOptions = this.LoadTextOptions; - var parseOptions = this.ParseOptions; - var textAndVersionSource = this.TextAndVersionSource; - var treeSource = this.TreeSource; + var lazyComputation = AsyncLazy.Create( + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (filePath: attributes.SyntaxTreeFilePath, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - var newTreeSource = GetReuseTreeSource( - filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer); + var newTreeSource = new LinkedFileReuseTreeAndVersionSource(originalTreeSource, lazyComputation); return new DocumentState( - languageServices, - Services, - Attributes, - _options, - siblingTextSource, - LoadTextOptions, - newTreeSource); - - static AsyncLazy GetReuseTreeSource( - string filePath, - LanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - AsyncLazy treeSource, - ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, - bool forceEvenIfTreesWouldDiffer) - { - return AsyncLazy.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - } + languageServices, services, attributes, parseOptions, siblingTextSource, loadTextOptions, newTreeSource); static bool TryReuseSiblingRoot( string filePath, @@ -186,9 +211,9 @@ static async Task TryReuseSiblingTreeAsync( LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, + ITreeAndVersionSource siblingTreeSource, bool forceEvenIfTreesWouldDiffer, CancellationToken cancellationToken) { @@ -209,9 +234,9 @@ static TreeAndVersion TryReuseSiblingTree( LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, + ITreeAndVersionSource siblingTreeSource, bool forceEvenIfTreesWouldDiffer, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs index 30eaca390f9cb..42553e056311c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs @@ -22,6 +22,12 @@ private sealed class TreeTextSource(AsyncLazy textSource, VersionSta public bool CanReloadText => false; + /// + /// Not created from a text loader. + /// + public TextLoader? TextLoader + => null; + public async Task GetValueAsync(LoadTextOptions options, CancellationToken cancellationToken) { var text = await textSource.GetValueAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/FilePathToDocumentIdsMap.cs b/src/Workspaces/Core/Portable/Workspace/Solution/FilePathToDocumentIdsMap.cs deleted file mode 100644 index 8bee3099b713a..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Solution/FilePathToDocumentIdsMap.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis; - -/// -/// Helper type that keeps track of all the file paths for all the documents in a solution snapshot and all the document -/// ids each maps to. -/// -internal readonly struct FilePathToDocumentIdsMap -{ - private static readonly ImmutableDictionary> s_emptyMap - = ImmutableDictionary.Create>(StringComparer.OrdinalIgnoreCase); - public static readonly FilePathToDocumentIdsMap Empty = new(isFrozen: false, s_emptyMap); - - /// - /// Whether or not this map corresponds to a frozen solution. Frozen solutions commonly drop many documents - /// (because only documents whose trees have been parsed are kept out). To keep things fast, instead of actually - /// dropping all those files from our we instead only keep track of added documents and mark that - /// we're frozen. Then, when a client actually asks for the document ids in a particular solution, we only return the - /// actual set present in that solution instance. - /// - public readonly bool IsFrozen; - private readonly ImmutableDictionary> _map; - - private FilePathToDocumentIdsMap(bool isFrozen, ImmutableDictionary> map) - { - IsFrozen = isFrozen; - _map = map; - } - - public bool TryGetValue(string filePath, out ImmutableArray documentIdsWithPath) - => _map.TryGetValue(filePath, out documentIdsWithPath); - - public static bool operator ==(FilePathToDocumentIdsMap left, FilePathToDocumentIdsMap right) - => left.IsFrozen == right.IsFrozen && left._map == right._map; - - public static bool operator !=(FilePathToDocumentIdsMap left, FilePathToDocumentIdsMap right) - => !(left == right); - - public override int GetHashCode() - => throw new NotSupportedException(); - - public override bool Equals([NotNullWhen(true)] object? obj) - => obj is FilePathToDocumentIdsMap map && Equals(map); - - public Builder ToBuilder() - => new(IsFrozen, _map.ToBuilder()); - - public FilePathToDocumentIdsMap ToFrozen() - => IsFrozen ? this : new(isFrozen: true, _map); - - public readonly struct Builder - { - private readonly bool _isFrozen; - private readonly ImmutableDictionary>.Builder _builder; - - public Builder(bool isFrozen, ImmutableDictionary>.Builder builder) - { - _isFrozen = isFrozen; - _builder = builder; - } - - public FilePathToDocumentIdsMap ToImmutable() - => new(_isFrozen, _builder.ToImmutable()); - - public bool TryGetValue(string filePath, out ImmutableArray documentIdsWithPath) - => _builder.TryGetValue(filePath, out documentIdsWithPath); - - public void Add(string? filePath, DocumentId documentId) - { - if (RoslynString.IsNullOrEmpty(filePath)) - return; - - _builder.MultiAdd(filePath, documentId); - } - - public void Remove(string? filePath, DocumentId documentId) - { - if (RoslynString.IsNullOrEmpty(filePath)) - return; - - if (!this.TryGetValue(filePath, out var documentIdsWithPath) || !documentIdsWithPath.Contains(documentId)) - throw new ArgumentException($"The given documentId was not found"); - - _builder.MultiRemove(filePath, documentId); - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs index 80fcf4203170b..db5c420b28c49 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs @@ -219,7 +219,7 @@ private ImmutableDictionary> ComputeRever } return reverseReferencesMap - .Select(kvp => new KeyValuePair>(kvp.Key, kvp.Value.ToImmutableHashSet())) + .Select(kvp => KeyValuePairUtil.Create(kvp.Key, kvp.Value.ToImmutableHashSet())) .ToImmutableDictionary(); } @@ -270,7 +270,7 @@ private ImmutableHashSet GetProjectsThatThisProjectTransitivelyDepend using var pooledObject = SharedPools.Default>().GetPooledObject(); var results = pooledObject.Object; this.ComputeTransitiveReferences(projectId, results); - transitiveReferences = results.ToImmutableHashSet(); + transitiveReferences = [.. results]; _transitiveReferencesMap = _transitiveReferencesMap.Add(projectId, transitiveReferences); } @@ -323,7 +323,7 @@ private ImmutableHashSet GetProjectsThatTransitivelyDependOnThisProje var results = pooledObject.Object; ComputeReverseTransitiveReferences(projectId, results); - reverseTransitiveReferences = results.ToImmutableHashSet(); + reverseTransitiveReferences = [.. results]; _reverseTransitiveReferencesMap = _reverseTransitiveReferencesMap.Add(projectId, reverseTransitiveReferences); } @@ -367,7 +367,7 @@ private void GetTopologicallySortedProjects_NoLock(CancellationToken cancellatio using var seenProjects = SharedPools.Default>().GetPooledObject(); using var resultList = SharedPools.Default>().GetPooledObject(); this.TopologicalSort(_projectIds, seenProjects.Object, resultList.Object, cancellationToken); - _lazyTopologicallySortedProjects = resultList.Object.ToImmutableArray(); + _lazyTopologicallySortedProjects = [.. resultList.Object]; } } @@ -419,7 +419,7 @@ private ImmutableArray> GetDependencySets_NoLock(Cancella using var seenProjects = SharedPools.Default>().GetPooledObject(); using var results = SharedPools.Default>>().GetPooledObject(); this.ComputeDependencySets(seenProjects.Object, results.Object, cancellationToken); - _lazyDependencySets = results.Object.ToImmutableArray(); + _lazyDependencySets = [.. results.Object]; } return _lazyDependencySets; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs index 56a51c3f5750a..d7e48d3bf3dd3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs @@ -66,7 +66,7 @@ private static ImmutableDictionary> Compu } else { - return existingReferencesMap.SetItem(projectId, referencedProjectIds.ToImmutableHashSet()); + return existingReferencesMap.SetItem(projectId, [.. referencedProjectIds]); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs index 645843fce454e..481b40cf1924b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; @@ -515,7 +514,6 @@ public ProjectAttributes With( VersionStamp? version = null, string? name = null, string? assemblyName = null, - string? language = null, Optional filePath = default, Optional outputPath = default, Optional outputRefPath = default, @@ -530,7 +528,6 @@ public ProjectAttributes With( var newVersion = version ?? Version; var newName = name ?? Name; var newAssemblyName = assemblyName ?? AssemblyName; - var newLanguage = language ?? Language; var newFilePath = filePath.HasValue ? filePath.Value : FilePath; var newOutputPath = outputPath.HasValue ? outputPath.Value : OutputFilePath; var newOutputRefPath = outputRefPath.HasValue ? outputRefPath.Value : OutputRefFilePath; @@ -545,7 +542,6 @@ public ProjectAttributes With( if (newVersion == Version && newName == Name && newAssemblyName == AssemblyName && - newLanguage == Language && newFilePath == FilePath && newOutputPath == OutputFilePath && newOutputRefPath == OutputRefFilePath && @@ -565,7 +561,7 @@ public ProjectAttributes With( newVersion, newName, newAssemblyName, - newLanguage, + Language, newCompilationOutputPaths, newChecksumAlgorithm, defaultNamespace: newDefaultNamespace, diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs index 6577677f41562..013c0f9aaa830 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; +using System.Collections.Frozen; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -14,11 +15,11 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -60,11 +61,6 @@ internal partial class ProjectState private AnalyzerOptions? _lazyAnalyzerOptions; - /// - /// The list of source generators and the analyzer reference they came from. - /// - private ImmutableDictionary? _lazySourceGenerators; - private ProjectState( ProjectInfo projectInfo, LanguageServices languageServices, @@ -736,38 +732,6 @@ public ProjectState WithAnalyzerReferences(IEnumerable analyz return With(projectInfo: ProjectInfo.WithAnalyzerReferences(analyzerReferences).WithVersion(Version.GetNewerVersion())); } - [MemberNotNull(nameof(_lazySourceGenerators))] - private void EnsureSourceGeneratorsInitialized() - { - if (_lazySourceGenerators == null) - { - var builder = ImmutableDictionary.CreateBuilder(); - - foreach (var analyzerReference in AnalyzerReferences) - { - foreach (var generator in analyzerReference.GetGenerators(Language)) - builder.Add(generator, analyzerReference); - } - - Interlocked.CompareExchange(ref _lazySourceGenerators, builder.ToImmutable(), comparand: null); - } - } - - public IEnumerable SourceGenerators - { - get - { - EnsureSourceGeneratorsInitialized(); - return _lazySourceGenerators.Keys; - } - } - - public AnalyzerReference GetAnalyzerReferenceForGenerator(ISourceGenerator generator) - { - EnsureSourceGeneratorsInitialized(); - return _lazySourceGenerators[generator]; - } - public ProjectState AddDocuments(ImmutableArray documents) { if (documents.IsEmpty) @@ -976,4 +940,18 @@ private void GetLatestDependentVersions( ? CreateLazyLatestDocumentTopLevelChangeVersion(newDocument, newDocumentStates, newAdditionalDocumentStates) : _lazyLatestDocumentTopLevelChangeVersion; } + + public void AddDocumentIdsWithFilePath(ref TemporaryArray temporaryArray, string filePath) + { + this.DocumentStates.AddDocumentIdsWithFilePath(ref temporaryArray, filePath); + this.AdditionalDocumentStates.AddDocumentIdsWithFilePath(ref temporaryArray, filePath); + this.AnalyzerConfigDocumentStates.AddDocumentIdsWithFilePath(ref temporaryArray, filePath); + } + + public DocumentId? GetFirstDocumentIdWithFilePath(string filePath) + { + return this.DocumentStates.GetFirstDocumentIdWithFilePath(filePath) ?? + this.AdditionalDocumentStates.GetFirstDocumentIdWithFilePath(filePath) ?? + this.AnalyzerConfigDocumentStates.GetFirstDocumentIdWithFilePath(filePath); + } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState_Checksum.cs index b7c7b5f570b3e..cad59046c71f9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState_Checksum.cs @@ -45,9 +45,9 @@ private async Task ComputeChecksumsAsync(CancellationToke { using (Logger.LogBlock(FunctionId.ProjectState_ComputeChecksumsAsync, FilePath, cancellationToken)) { - var documentChecksumsTask = DocumentStates.GetChecksumsAndIdsAsync(cancellationToken); - var additionalDocumentChecksumsTask = AdditionalDocumentStates.GetChecksumsAndIdsAsync(cancellationToken); - var analyzerConfigDocumentChecksumsTask = AnalyzerConfigDocumentStates.GetChecksumsAndIdsAsync(cancellationToken); + var documentChecksumsTask = DocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken); + var additionalDocumentChecksumsTask = AdditionalDocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken); + var analyzerConfigDocumentChecksumsTask = AnalyzerConfigDocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken); var serializer = LanguageServices.SolutionServices.GetService(); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index cafe4e3d878c3..df13aee1dcd6a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -193,6 +194,14 @@ private static Project CreateProject(ProjectId projectId, Solution solution) internal Project? GetOriginatingProject(ISymbol symbol) => GetProject(GetOriginatingProjectId(symbol)); + /// + /// + /// Returns the that produced the symbol. In the case of a symbol that was retargetted + /// this will be the compilation it was retargtted into, not the original compilation that it was retargetted from. + /// + internal Compilation? GetOriginatingCompilation(ISymbol symbol) + => _compilationState.GetOriginatingProjectInfo(symbol)?.Compilation; + /// /// True if the solution contains the document in one of its projects /// @@ -325,6 +334,9 @@ private static Project CreateProject(ProjectId projectId, Solution solution) return null; } + private Solution WithCompilationState(SolutionCompilationState compilationState) + => compilationState == _compilationState ? this : new Solution(compilationState); + /// /// Creates a new solution instance that includes a project with the specified language and names. /// Returns the new project. @@ -341,24 +353,30 @@ public Project AddProject(string name, string assemblyName, string language) public Solution AddProject(ProjectId projectId, string name, string assemblyName, string language) => this.AddProject(ProjectInfo.Create(projectId, VersionStamp.Create(), name, assemblyName, language)); - /// - /// Create a new solution instance that includes a project with the specified project information. - /// + /// public Solution AddProject(ProjectInfo projectInfo) { - var newCompilationState = _compilationState.AddProject(projectInfo); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + using var _ = ArrayBuilder.GetInstance(1, out var projectInfos); + projectInfos.Add(projectInfo); + return AddProjects(projectInfos); } - /// - /// Create a new solution instance without the project specified. - /// + /// + internal Solution AddProjects(ArrayBuilder projectInfos) + => WithCompilationState(_compilationState.AddProjects(projectInfos)); + + /// public Solution RemoveProject(ProjectId projectId) { - var newCompilationState = _compilationState.RemoveProject(projectId); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + using var _ = ArrayBuilder.GetInstance(1, out var projectIds); + projectIds.Add(projectId); + return RemoveProjects(projectIds); } + /// + internal Solution RemoveProjects(ArrayBuilder projectIds) + => WithCompilationState(_compilationState.RemoveProjects(projectIds)); + /// /// Creates a new solution instance with the project specified updated to have the new /// assembly name. @@ -372,8 +390,7 @@ public Solution WithProjectAssemblyName(ProjectId projectId, string assemblyName throw new ArgumentNullException(nameof(assemblyName)); } - var newCompilationState = _compilationState.WithProjectAssemblyName(projectId, assemblyName); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithProjectAssemblyName(projectId, assemblyName)); } /// @@ -383,8 +400,7 @@ public Solution WithProjectOutputFilePath(ProjectId projectId, string? outputFil { CheckContainsProject(projectId); - var newCompilationState = _compilationState.WithProjectOutputFilePath(projectId, outputFilePath); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithProjectOutputFilePath(projectId, outputFilePath)); } /// @@ -394,8 +410,7 @@ public Solution WithProjectOutputRefFilePath(ProjectId projectId, string? output { CheckContainsProject(projectId); - var newCompilationState = _compilationState.WithProjectOutputRefFilePath(projectId, outputRefFilePath); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithProjectOutputRefFilePath(projectId, outputRefFilePath)); } /// @@ -405,8 +420,7 @@ public Solution WithProjectCompilationOutputInfo(ProjectId projectId, in Compila { CheckContainsProject(projectId); - var newCompilationState = _compilationState.WithProjectCompilationOutputInfo(projectId, info); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithProjectCompilationOutputInfo(projectId, info)); } /// @@ -416,8 +430,7 @@ public Solution WithProjectDefaultNamespace(ProjectId projectId, string? default { CheckContainsProject(projectId); - var newCompilationState = _compilationState.WithProjectDefaultNamespace(projectId, defaultNamespace); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithProjectDefaultNamespace(projectId, defaultNamespace)); } /// @@ -427,8 +440,7 @@ internal Solution WithProjectChecksumAlgorithm(ProjectId projectId, SourceHashAl { CheckContainsProject(projectId); - var newCompilationState = _compilationState.WithProjectChecksumAlgorithm(projectId, checksumAlgorithm); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithProjectChecksumAlgorithm(projectId, checksumAlgorithm)); } /// @@ -443,8 +455,7 @@ public Solution WithProjectName(ProjectId projectId, string name) throw new ArgumentNullException(nameof(name)); } - var newCompilationState = _compilationState.WithProjectName(projectId, name); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithProjectName(projectId, name)); } /// @@ -454,8 +465,7 @@ public Solution WithProjectFilePath(ProjectId projectId, string? filePath) { CheckContainsProject(projectId); - var newCompilationState = _compilationState.WithProjectFilePath(projectId, filePath); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithProjectFilePath(projectId, filePath)); } /// @@ -471,8 +481,7 @@ public Solution WithProjectCompilationOptions(ProjectId projectId, CompilationOp throw new ArgumentNullException(nameof(options)); } - var newCompilationState = _compilationState.WithProjectCompilationOptions(projectId, options); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithProjectCompilationOptions(projectId, options)); } /// @@ -488,8 +497,7 @@ public Solution WithProjectParseOptions(ProjectId projectId, ParseOptions option throw new ArgumentNullException(nameof(options)); } - var newCompilationState = _compilationState.WithProjectParseOptions(projectId, options); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithProjectParseOptions(projectId, options)); } /// @@ -501,8 +509,7 @@ internal Solution WithHasAllInformation(ProjectId projectId, bool hasAllInformat { CheckContainsProject(projectId); - var newCompilationState = _compilationState.WithHasAllInformation(projectId, hasAllInformation); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithHasAllInformation(projectId, hasAllInformation)); } /// @@ -514,8 +521,7 @@ internal Solution WithRunAnalyzers(ProjectId projectId, bool runAnalyzers) { CheckContainsProject(projectId); - var newCompilationState = _compilationState.WithRunAnalyzers(projectId, runAnalyzers); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithRunAnalyzers(projectId, runAnalyzers)); } /// @@ -536,8 +542,7 @@ public Solution WithProjectDocumentsOrder(ProjectId projectId, ImmutableList @@ -551,8 +556,7 @@ public Solution WithProjectDocumentsOrder(ProjectId projectId, ImmutableList @@ -585,8 +589,7 @@ public Solution AddProjectReferences(ProjectId projectId, IEnumerable @@ -607,8 +610,7 @@ public Solution RemoveProjectReference(ProjectId projectId, ProjectReference pro if (!oldProject.ProjectReferences.Contains(projectReference)) throw new ArgumentException(WorkspacesResources.Project_does_not_contain_specified_reference, nameof(projectReference)); - var newCompilationState = _compilationState.RemoveProjectReference(projectId, projectReference); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.RemoveProjectReference(projectId, projectReference)); } /// @@ -631,8 +633,7 @@ public Solution WithProjectReferences(ProjectId projectId, IEnumerable @@ -646,8 +647,7 @@ public Solution WithProjectReferences(ProjectId projectId, IEnumerable @@ -675,8 +675,7 @@ public Solution AddMetadataReferences(ProjectId projectId, IEnumerable @@ -698,8 +697,7 @@ public Solution RemoveMetadataReference(ProjectId projectId, MetadataReference m if (!oldProject.MetadataReferences.Contains(metadataReference)) throw new InvalidOperationException(WorkspacesResources.Project_does_not_contain_specified_reference); - var newCompilationState = _compilationState.RemoveMetadataReference(projectId, metadataReference); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.RemoveMetadataReference(projectId, metadataReference)); } /// @@ -716,8 +714,7 @@ public Solution WithProjectMetadataReferences(ProjectId projectId, IEnumerable @@ -730,8 +727,7 @@ public Solution WithProjectMetadataReferences(ProjectId projectId, IEnumerable @@ -764,8 +760,7 @@ public Solution AddAnalyzerReferences(ProjectId projectId, IEnumerable @@ -787,8 +782,7 @@ public Solution RemoveAnalyzerReference(ProjectId projectId, AnalyzerReference a if (!oldProject.AnalyzerReferences.Contains(analyzerReference)) throw new InvalidOperationException(WorkspacesResources.Project_does_not_contain_specified_reference); - var newCompilationState = _compilationState.RemoveAnalyzerReference(projectId, analyzerReference); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.RemoveAnalyzerReference(projectId, analyzerReference)); } /// @@ -805,8 +799,7 @@ public Solution WithProjectAnalyzerReferences(ProjectId projectId, IEnumerable @@ -816,8 +809,7 @@ public Solution WithProjectAnalyzerReferences(ProjectId projectId, IEnumerable @@ -841,8 +833,7 @@ public Solution AddAnalyzerReferences(IEnumerable analyzerRef } } - var newCompilationState = _compilationState.AddAnalyzerReferences(collection); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.AddAnalyzerReferences(collection)); } /// @@ -859,8 +850,7 @@ public Solution RemoveAnalyzerReference(AnalyzerReference analyzerReference) if (!this.SolutionState.AnalyzerReferences.Contains(analyzerReference)) throw new InvalidOperationException(WorkspacesResources.Solution_does_not_contain_specified_reference); - var newCompilationState = _compilationState.RemoveAnalyzerReference(analyzerReference); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.RemoveAnalyzerReference(analyzerReference)); } /// @@ -872,8 +862,7 @@ public Solution WithAnalyzerReferences(IEnumerable analyzerRe { var collection = PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences)); - var newCompilationState = _compilationState.WithAnalyzerReferences(collection); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithAnalyzerReferences(collection)); } private static SourceCodeKind GetSourceCodeKind(ProjectState project) @@ -986,10 +975,7 @@ public Solution AddDocument(DocumentInfo documentInfo) /// /// A new with the documents added. public Solution AddDocuments(ImmutableArray documentInfos) - { - var newCompilationState = _compilationState.AddDocuments(documentInfos); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.AddDocuments(documentInfos)); /// /// Creates a new solution instance with the corresponding project updated to include a new @@ -1027,10 +1013,7 @@ public Solution AddAdditionalDocument(DocumentInfo documentInfo) => AddAdditionalDocuments([documentInfo]); public Solution AddAdditionalDocuments(ImmutableArray documentInfos) - { - var newCompilationState = _compilationState.AddAdditionalDocuments(documentInfos); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.AddAdditionalDocuments(documentInfos)); /// /// Creates a new solution instance with the corresponding project updated to include a new @@ -1082,10 +1065,7 @@ private ProjectState GetRequiredProjectState(ProjectId projectId) /// Creates a new Solution instance that contains a new compiler configuration document like a .editorconfig file. /// public Solution AddAnalyzerConfigDocuments(ImmutableArray documentInfos) - { - var newCompilationState = _compilationState.AddAnalyzerConfigDocuments(documentInfos); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.AddAnalyzerConfigDocuments(documentInfos)); /// /// Creates a new solution instance that no longer includes the specified document. @@ -1106,10 +1086,7 @@ public Solution RemoveDocuments(ImmutableArray documentIds) } private Solution RemoveDocumentsImpl(ImmutableArray documentIds) - { - var newCompilationState = _compilationState.RemoveDocuments(documentIds); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.RemoveDocuments(documentIds)); /// /// Creates a new solution instance that no longer includes the specified additional document. @@ -1130,10 +1107,7 @@ public Solution RemoveAdditionalDocuments(ImmutableArray documentIds } private Solution RemoveAdditionalDocumentsImpl(ImmutableArray documentIds) - { - var newCompilationState = _compilationState.RemoveAdditionalDocuments(documentIds); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.RemoveAdditionalDocuments(documentIds)); /// /// Creates a new solution instance that no longer includes the specified . @@ -1154,10 +1128,7 @@ public Solution RemoveAnalyzerConfigDocuments(ImmutableArray documen } private Solution RemoveAnalyzerConfigDocumentsImpl(ImmutableArray documentIds) - { - var newCompilationState = _compilationState.RemoveAnalyzerConfigDocuments(documentIds); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.RemoveAnalyzerConfigDocuments(documentIds)); /// /// Creates a new solution instance with the document specified updated to have the new name. @@ -1171,8 +1142,7 @@ public Solution WithDocumentName(DocumentId documentId, string name) throw new ArgumentNullException(nameof(name)); } - var newCompilationState = _compilationState.WithDocumentName(documentId, name); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithDocumentName(documentId, name)); } /// @@ -1185,8 +1155,7 @@ public Solution WithDocumentFolders(DocumentId documentId, IEnumerable? var collection = PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)); - var newCompilationState = _compilationState.WithDocumentFolders(documentId, collection); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithDocumentFolders(documentId, collection)); } /// @@ -1204,8 +1173,7 @@ public Solution WithDocumentFilePath(DocumentId documentId, string filePath) throw new ArgumentNullException(nameof(filePath)); } - var newCompilationState = _compilationState.WithDocumentFilePath(documentId, filePath); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithDocumentFilePath(documentId, filePath)); } /// @@ -1226,8 +1194,7 @@ public Solution WithDocumentText(DocumentId documentId, SourceText text, Preserv throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.WithDocumentText(documentId, text, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithDocumentText(documentId, text, mode)); } /// @@ -1248,8 +1215,7 @@ public Solution WithAdditionalDocumentText(DocumentId documentId, SourceText tex throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.WithAdditionalDocumentText(documentId, text, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithAdditionalDocumentText(documentId, text, mode)); } /// @@ -1270,8 +1236,7 @@ public Solution WithAnalyzerConfigDocumentText(DocumentId documentId, SourceText throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.WithAnalyzerConfigDocumentText(documentId, text, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithAnalyzerConfigDocumentText(documentId, text, mode)); } /// @@ -1292,8 +1257,7 @@ public Solution WithDocumentText(DocumentId documentId, TextAndVersion textAndVe throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.WithDocumentText(documentId, textAndVersion, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithDocumentText(documentId, textAndVersion, mode)); } /// @@ -1314,8 +1278,7 @@ public Solution WithAdditionalDocumentText(DocumentId documentId, TextAndVersion throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.WithAdditionalDocumentText(documentId, textAndVersion, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithAdditionalDocumentText(documentId, textAndVersion, mode)); } /// @@ -1336,8 +1299,7 @@ public Solution WithAnalyzerConfigDocumentText(DocumentId documentId, TextAndVer throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.WithAnalyzerConfigDocumentText(documentId, textAndVersion, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithAnalyzerConfigDocumentText(documentId, textAndVersion, mode)); } /// @@ -1358,15 +1320,11 @@ public Solution WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode root, P throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.WithDocumentSyntaxRoot(documentId, root, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithDocumentSyntaxRoot(documentId, root, mode)); } internal Solution WithDocumentContentsFrom(DocumentId documentId, DocumentState documentState, bool forceEvenIfTreesWouldDiffer) - { - var newCompilationState = _compilationState.WithDocumentContentsFrom(documentId, documentState, forceEvenIfTreesWouldDiffer); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.WithDocumentContentsFrom(documentId, documentState, forceEvenIfTreesWouldDiffer)); /// /// Creates a new solution instance with the document specified updated to have the source @@ -1388,8 +1346,7 @@ public Solution WithDocumentSourceCodeKind(DocumentId documentId, SourceCodeKind throw new ArgumentOutOfRangeException(nameof(sourceCodeKind)); } - var newCompilationState = _compilationState.WithDocumentSourceCodeKind(documentId, sourceCodeKind); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithDocumentSourceCodeKind(documentId, sourceCodeKind)); } /// @@ -1410,8 +1367,7 @@ public Solution WithDocumentTextLoader(DocumentId documentId, TextLoader loader, throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.UpdateDocumentTextLoader(documentId, loader, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.UpdateDocumentTextLoader(documentId, loader, mode)); } /// @@ -1432,8 +1388,7 @@ public Solution WithAdditionalDocumentTextLoader(DocumentId documentId, TextLoad throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.UpdateAdditionalDocumentTextLoader(documentId, loader, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.UpdateAdditionalDocumentTextLoader(documentId, loader, mode)); } /// @@ -1454,8 +1409,7 @@ public Solution WithAnalyzerConfigDocumentTextLoader(DocumentId documentId, Text throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.UpdateAnalyzerConfigDocumentTextLoader(documentId, loader, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.UpdateAnalyzerConfigDocumentTextLoader(documentId, loader, mode)); } /// @@ -1519,7 +1473,14 @@ static AsyncLazy CreateLazyFrozenSolution(SolutionCompilationState com static Solution ComputeFrozenSolution(SolutionCompilationState compilationState, DocumentId documentId, CancellationToken cancellationToken) { var newCompilationState = compilationState.WithFrozenPartialCompilationIncludingSpecificDocument(documentId, cancellationToken); - return new Solution(newCompilationState); + var solution = new Solution(newCompilationState); + + // ensure that this document is within the frozen-partial-document for the solution we're creating. That + // way, if we ask to freeze it again, we'll just the same document back. + Contract.ThrowIfTrue(solution._documentIdToFrozenSolution.Count != 0); + solution._documentIdToFrozenSolution.Add(documentId, AsyncLazy.Create(solution)); + + return solution; } } @@ -1536,15 +1497,20 @@ internal async Task WithMergedLinkedFileChangesAsync( } internal ImmutableArray GetRelatedDocumentIds(DocumentId documentId) - { - return this.SolutionState.GetRelatedDocumentIds(documentId); - } + => this.SolutionState.GetRelatedDocumentIds(documentId); + + /// + /// Returns one of any of the related documents of . Importantly, this will never + /// return (unlike which includes the original + /// file in the result). + /// + /// A hint on the first project to search when looking for related + /// documents. Must not be the project that is from. + internal DocumentId? GetFirstRelatedDocumentId(DocumentId documentId, ProjectId? relatedProjectIdHint) + => this.SolutionState.GetFirstRelatedDocumentId(documentId, relatedProjectIdHint); internal Solution WithNewWorkspace(string? workspaceKind, int workspaceVersion, SolutionServices services) - { - var newCompilationState = _compilationState.WithNewWorkspace(workspaceKind, workspaceVersion, services); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.WithNewWorkspace(workspaceKind, workspaceVersion, services)); /// /// Formerly, returned a copy of the solution isolated from the original so that they do not share computed state. It now does nothing. @@ -1577,8 +1543,7 @@ public Solution WithDocumentText(IEnumerable documentIds, SourceTex throw new ArgumentOutOfRangeException(nameof(mode)); } - var newCompilationState = _compilationState.WithDocumentText(documentIds, text, mode); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); + return WithCompilationState(_compilationState.WithDocumentText(documentIds, text, mode)); } /// @@ -1586,12 +1551,11 @@ public Solution WithDocumentText(IEnumerable documentIds, SourceTex /// 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) + internal Document WithFrozenSourceGeneratedDocument( + SourceGeneratedDocumentIdentity documentIdentity, DateTime generationDateTime, SourceText text) { - var newCompilationState = _compilationState.WithFrozenSourceGeneratedDocuments([(documentIdentity, text)]); - var newSolution = newCompilationState != _compilationState - ? new Solution(newCompilationState) - : this; + var newCompilationState = _compilationState.WithFrozenSourceGeneratedDocuments([(documentIdentity, generationDateTime, text)]); + var newSolution = WithCompilationState(newCompilationState); var newDocumentState = newCompilationState.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentIdentity.DocumentId); Contract.ThrowIfNull(newDocumentState, "Because we just froze this document, it should always exist."); @@ -1600,23 +1564,18 @@ internal Document WithFrozenSourceGeneratedDocument(SourceGeneratedDocumentIdent return newProject.GetOrCreateSourceGeneratedDocument(newDocumentState); } - internal Solution WithFrozenSourceGeneratedDocuments(ImmutableArray<(SourceGeneratedDocumentIdentity documentIdentity, SourceText text)> documents) - { - var newCompilationState = _compilationState.WithFrozenSourceGeneratedDocuments(documents); - return newCompilationState != _compilationState - ? new Solution(newCompilationState) - : this; - } + internal Solution WithFrozenSourceGeneratedDocuments(ImmutableArray<(SourceGeneratedDocumentIdentity documentIdentity, DateTime generationDateTime, SourceText text)> documents) + => WithCompilationState(_compilationState.WithFrozenSourceGeneratedDocuments(documents)); + + internal Solution WithSourceGeneratorExecutionVersions(SourceGeneratorExecutionVersionMap sourceGeneratorExecutionVersionMap, CancellationToken cancellationToken) + => WithCompilationState(_compilationState.WithSourceGeneratorExecutionVersions(sourceGeneratorExecutionVersionMap, cancellationToken)); /// /// Undoes the operation of ; any frozen source generated document is allowed /// to have it's real output again. /// internal Solution WithoutFrozenSourceGeneratedDocuments() - { - var newCompilationState = _compilationState.WithoutFrozenSourceGeneratedDocuments(); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.WithoutFrozenSourceGeneratedDocuments()); /// /// Returns a new Solution which represents the same state as before, but with the cached generator driver state from the given project updated to match. @@ -1627,10 +1586,7 @@ internal Solution WithoutFrozenSourceGeneratedDocuments() /// the cached state. /// internal Solution WithCachedSourceGeneratorState(ProjectId projectToUpdate, Project projectWithCachedGeneratorState) - { - var newCompilationState = _compilationState.WithCachedSourceGeneratorState(projectToUpdate, projectWithCachedGeneratorState); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.WithCachedSourceGeneratorState(projectToUpdate, projectWithCachedGeneratorState)); /// /// Gets an objects that lists the added, changed and removed projects between @@ -1685,10 +1641,7 @@ public Solution WithOptions(OptionSet options) /// Creates a new solution instance with the specified serializable . /// internal Solution WithOptions(SolutionOptionSet options) - { - var newCompilationState = _compilationState.WithOptions(options); - return newCompilationState == _compilationState ? this : new Solution(newCompilationState); - } + => WithCompilationState(_compilationState.WithOptions(options)); private void CheckContainsProject(ProjectId projectId) { @@ -1837,4 +1790,7 @@ private void CheckSubmissionProjectReferences(ProjectId projectId, IEnumerable

this.CompilationState.SourceGeneratorExecutionVersionMap[projectId]; } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs index bf93c6484c821..347957df7875f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs @@ -35,8 +35,11 @@ private abstract class CompilationTrackerState /// whether we even have references -- it's pretty likely that running a generator might produce worse results /// than what we originally had. /// + /// This also controls if we will generate skeleton references for cross-language P2P references when + /// creating the compilation for a particular project. When entirely frozen, we do not want to do this due + /// to the enormous cost of emitting ref assemblies for cross language cases. ///

- public readonly bool IsFrozen; + public readonly CreationPolicy CreationPolicy; /// /// The best compilation that is available that source generators have not ran on. May be an @@ -47,10 +50,10 @@ private abstract class CompilationTrackerState public CompilationTrackerGeneratorInfo GeneratorInfo { get; } protected CompilationTrackerState( - bool isFrozen, + CreationPolicy creationPolicy, CompilationTrackerGeneratorInfo generatorInfo) { - IsFrozen = isFrozen; + CreationPolicy = creationPolicy; GeneratorInfo = generatorInfo; } } @@ -80,12 +83,12 @@ private sealed class InProgressState : CompilationTrackerState public override Compilation CompilationWithoutGeneratedDocuments => LazyCompilationWithoutGeneratedDocuments.Value; public InProgressState( - bool isFrozen, + CreationPolicy creationPolicy, Lazy compilationWithoutGeneratedDocuments, CompilationTrackerGeneratorInfo generatorInfo, Lazy staleCompilationWithGeneratedDocuments, ImmutableList pendingTranslationActions) - : base(isFrozen, generatorInfo) + : base(creationPolicy, generatorInfo) { // Note: Intermediate projects can be empty. Contract.ThrowIfTrue(pendingTranslationActions is null); @@ -105,13 +108,13 @@ public InProgressState( } public InProgressState( - bool isFrozen, + CreationPolicy creationPolicy, Compilation compilationWithoutGeneratedDocuments, CompilationTrackerGeneratorInfo generatorInfo, Compilation? staleCompilationWithGeneratedDocuments, ImmutableList pendingTranslationActions) : this( - isFrozen, + creationPolicy, new Lazy(() => compilationWithoutGeneratedDocuments), generatorInfo, // Extracted as a method call to prevent captures. @@ -144,12 +147,9 @@ private sealed class FinalCompilationTrackerState : CompilationTrackerState public readonly bool HasSuccessfullyLoaded; /// - /// Weak set of the assembly, module and dynamic symbols that this compilation tracker has created. - /// This can be used to determine which project an assembly symbol came from after the fact. This is - /// needed as the compilation an assembly came from can be GC'ed and further requests to get that - /// compilation (or any of it's assemblies) may produce new assembly symbols. + /// Used to determine which project an assembly symbol came from after the fact. /// - public readonly UnrootedSymbolSet UnrootedSymbolSet; + private SingleInitNullable _rootedSymbolSet; /// /// The final compilation, with all references and source generators run. This is distinct from Not held onto /// Not held onto public static FinalCompilationTrackerState Create( - bool isFrozen, + CreationPolicy creationPolicy, Compilation finalCompilationWithGeneratedDocuments, Compilation compilationWithoutGeneratedDocuments, bool hasSuccessfullyLoaded, @@ -213,25 +211,28 @@ public static FinalCompilationTrackerState 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 = UnrootedSymbolSet.Create(finalCompilationWithGeneratedDocuments); RecordAssemblySymbols(projectId, finalCompilationWithGeneratedDocuments, metadataReferenceToProjectId); return new FinalCompilationTrackerState( - isFrozen, + creationPolicy, finalCompilationWithGeneratedDocuments, compilationWithoutGeneratedDocuments, hasSuccessfullyLoaded, - generatorInfo, - unrootedSymbolSet); + generatorInfo); } - public FinalCompilationTrackerState WithIsFrozen() - => new(isFrozen: true, - FinalCompilationWithGeneratedDocuments, - CompilationWithoutGeneratedDocuments, - HasSuccessfullyLoaded, - GeneratorInfo, - UnrootedSymbolSet); + public RootedSymbolSet RootedSymbolSet => _rootedSymbolSet.Initialize( + static finalCompilationWithGeneratedDocuments => RootedSymbolSet.Create(finalCompilationWithGeneratedDocuments), + this.FinalCompilationWithGeneratedDocuments); + + public FinalCompilationTrackerState WithCreationPolicy(CreationPolicy creationPolicy) + => creationPolicy == this.CreationPolicy + ? this + : new(creationPolicy, + FinalCompilationWithGeneratedDocuments, + CompilationWithoutGeneratedDocuments, + HasSuccessfullyLoaded, + GeneratorInfo); private static void RecordAssemblySymbols(ProjectId projectId, Compilation compilation, Dictionary? metadataReferenceToProjectId) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index 4134f3996f5ce..5f1b44eaf6799 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -100,23 +100,22 @@ public GeneratorDriver? GeneratorDriver } } - public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough) + public bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough) { - Debug.Assert(symbol.Kind is SymbolKind.Assembly or - SymbolKind.NetModule or - SymbolKind.DynamicType); - var state = this.ReadState(); - - var unrootedSymbolSet = (state as FinalCompilationTrackerState)?.UnrootedSymbolSet; - if (unrootedSymbolSet == null) + Debug.Assert(symbol.Kind is SymbolKind.Assembly or SymbolKind.NetModule or SymbolKind.DynamicType); + if (this.ReadState() is not FinalCompilationTrackerState finalState) { // this was not a tracker that has handed out a compilation (all compilations handed out must be // owned by a 'FinalState'). So this symbol could not be from us. + compilation = null; referencedThrough = null; return false; } - return unrootedSymbolSet.Value.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out referencedThrough); + return finalState.RootedSymbolSet.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out compilation, out referencedThrough); } /// @@ -152,7 +151,7 @@ public ICompilationTracker Fork( }; var newState = new InProgressState( - state.IsFrozen, + state.CreationPolicy, compilationWithoutGeneratedDocuments, state.GeneratorInfo, staleCompilationWithGeneratedDocuments, @@ -334,12 +333,13 @@ InProgressState BuildInProgressStateFromNoCompilationState() var compilationWithoutGeneratedDocuments = CreateEmptyCompilation(); - // We only got here when we had no compilation state at all. So we couldn't have gotten - // here from a frozen state (as a frozen state always ensures we have a - // WithCompilationTrackerState). As such, we can safely still preserve that we're not - // frozen here. + // We only got here when we had no compilation state at all. So we couldn't have gotten here + // from a frozen state (as a frozen state always ensures we have at least an InProgressState). + // As such, we want to start initially in the state where we will both run generators and create + // skeleton references for p2p references. That will ensure the most correct state for our + // compilation the first time we create it. var allSyntaxTreesParsedState = new InProgressState( - isFrozen: false, + CreationPolicy.Create, new Lazy(CreateEmptyCompilation), CompilationTrackerGeneratorInfo.Empty, staleCompilationWithGeneratedDocuments: s_lazyNullCompilation, @@ -358,6 +358,9 @@ async Task CollapseInProgressStateAsync(InProgressState initial { try { + // Only bother keeping track of staleCompilationWithGeneratedDocuments for projects that + // actually have generators in them. + var hasSourceGenerators = await compilationState.HasSourceGeneratorsAsync(this.ProjectState.Id, cancellationToken).ConfigureAwait(false); var currentState = initialState; // Then, we serially process the chain while that parsing is happening concurrently. @@ -367,7 +370,7 @@ async Task CollapseInProgressStateAsync(InProgressState initial // We have a list of transformations to get to our final compilation; take the first transformation and apply it. var (compilationWithoutGeneratedDocuments, staleCompilationWithGeneratedDocuments, generatorInfo) = - await ApplyFirstTransformationAsync(currentState).ConfigureAwait(false); + await ApplyFirstTransformationAsync(currentState, hasSourceGenerators).ConfigureAwait(false); // 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. @@ -380,7 +383,7 @@ async Task CollapseInProgressStateAsync(InProgressState initial // generator docs back to the uncomputed state from that point onwards. We'll just keep // whateverZ generated docs we have. currentState = new InProgressState( - currentState.IsFrozen, + currentState.CreationPolicy, compilationWithoutGeneratedDocuments, generatorInfo, staleCompilationWithGeneratedDocuments, @@ -396,7 +399,7 @@ async Task CollapseInProgressStateAsync(InProgressState initial } async Task<(Compilation compilationWithoutGeneratedDocuments, Compilation? staleCompilationWithGeneratedDocuments, CompilationTrackerGeneratorInfo generatorInfo)> - ApplyFirstTransformationAsync(InProgressState inProgressState) + ApplyFirstTransformationAsync(InProgressState inProgressState, bool hasSourceGenerators) { Contract.ThrowIfTrue(inProgressState.PendingTranslationActions.IsEmpty); var translationAction = inProgressState.PendingTranslationActions[0]; @@ -421,7 +424,7 @@ async Task CollapseInProgressStateAsync(InProgressState initial // 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 (translationAction.CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput && - translationAction.OldProjectState.SourceGenerators.Any()) + hasSourceGenerators) { staleCompilationWithGeneratedDocuments = await translationAction.TransformCompilationAsync(staleCompilationWithGeneratedDocuments, cancellationToken).ConfigureAwait(false); } @@ -471,8 +474,7 @@ async Task FinalizeCompilationWorkerAsync(InProgre // Caller should collapse the in progress state first. Contract.ThrowIfTrue(inProgressState.PendingTranslationActions.Count > 0); - // The final state we produce will be frozen or not depending on if a frozen state was passed into it. - var isFrozen = inProgressState.IsFrozen; + var creationPolicy = inProgressState.CreationPolicy; var generatorInfo = inProgressState.GeneratorInfo; var compilationWithoutGeneratedDocuments = inProgressState.CompilationWithoutGeneratedDocuments; var staleCompilationWithGeneratedDocuments = inProgressState.LazyStaleCompilationWithGeneratedDocuments.Value; @@ -524,27 +526,40 @@ await compilationState.GetCompilationAsync( { // Not a submission. Add as a metadata reference. - if (isFrozen) + if (creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.Create) { - // In the frozen case, attempt to get a partial reference, or fallback to the last - // successful reference for this project if we can find one. - var metadataReference = compilationState.GetPartialMetadataReference(projectReference, this.ProjectState); - + // Client always wants an up to date metadata reference. Produce one for this project + // reference. Because the policy is to always 'Create' here, we include cross language + // references, producing skeletons for them if necessary. + var metadataReference = await compilationState.GetMetadataReferenceAsync( + projectReference, this.ProjectState, includeCrossLanguage: true, cancellationToken).ConfigureAwait(false); + AddMetadataReference(projectReference, metadataReference); + } + else + { + Contract.ThrowIfFalse(creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.CreateIfAbsent or SkeletonReferenceCreationPolicy.DoNotCreate); + + // Client does not want to force a skeleton reference to be created. Try to get a + // metadata reference cheaply in the case where this is a reference to the same + // language. If that fails, also attempt to get a reference to a skeleton assembly + // produced from one of our prior stale compilations. + var metadataReference = await compilationState.GetMetadataReferenceAsync( + projectReference, this.ProjectState, includeCrossLanguage: false, cancellationToken).ConfigureAwait(false); if (metadataReference is null) { - // if we failed to get the metadata and we were frozen, check to see if we - // previously had existing metadata and reuse it instead. var inProgressCompilationNotRef = staleCompilationWithGeneratedDocuments ?? compilationWithoutGeneratedDocuments; metadataReference = inProgressCompilationNotRef.ExternalReferences.FirstOrDefault( r => GetProjectId(inProgressCompilationNotRef.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol) == projectReference.ProjectId); } - AddMetadataReference(projectReference, metadataReference); - } - else - { - // For the non-frozen case, attempt to get the full metadata reference. - var metadataReference = await compilationState.GetMetadataReferenceAsync(projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false); + // If we still failed, but our policy is to create when absent, then do the work to + // create a real skeleton here. + if (metadataReference is null && creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.CreateIfAbsent) + { + metadataReference = await compilationState.GetMetadataReferenceAsync( + projectReference, this.ProjectState, includeCrossLanguage: true, cancellationToken).ConfigureAwait(false); + } + AddMetadataReference(projectReference, metadataReference); } } @@ -562,19 +577,35 @@ await compilationState.GetCompilationAsync( } // We will finalize the compilation by adding full contents here. - var (compilationWithGeneratedDocuments, generatedDocuments, generatorDriver) = await AddExistingOrComputeNewGeneratorInfoAsync( - isFrozen, + var (compilationWithGeneratedDocuments, nextGeneratorInfo) = await AddExistingOrComputeNewGeneratorInfoAsync( + creationPolicy, compilationState, compilationWithoutGeneratedDocuments, generatorInfo, staleCompilationWithGeneratedDocuments, cancellationToken).ConfigureAwait(false); - // After producing the sg documents, we must always be in the final state for the generator data. - var nextGeneratorInfo = new CompilationTrackerGeneratorInfo(generatedDocuments, generatorDriver); + // If the user has the option set to only run generators to something other than 'automatic' then we + // want to set ourselves to not run generators again now that generators have run. That way, any + // further *automatic* changes to the solution will not run generators again. Instead, when one of + // those external events happen, we'll grab the workspace's solution, transition all states *out* of + // this state and then let the next 'GetCompilationAsync' operation cause generators to run. + // + // Similarly, we don't want to automatically create skeletons at this point (unless they're missing + // entirely). + + var workspacePreference = compilationState.Services.GetRequiredService().Options.SourceGeneratorExecution; + if (workspacePreference != SourceGeneratorExecutionPreference.Automatic) + { + if (creationPolicy.GeneratedDocumentCreationPolicy == GeneratedDocumentCreationPolicy.Create) + creationPolicy = creationPolicy with { GeneratedDocumentCreationPolicy = GeneratedDocumentCreationPolicy.DoNotCreate }; + + if (creationPolicy.SkeletonReferenceCreationPolicy == SkeletonReferenceCreationPolicy.Create) + creationPolicy = creationPolicy with { SkeletonReferenceCreationPolicy = SkeletonReferenceCreationPolicy.CreateIfAbsent }; + } var finalState = FinalCompilationTrackerState.Create( - isFrozen, + creationPolicy, compilationWithGeneratedDocuments, compilationWithoutGeneratedDocuments, hasSuccessfullyLoaded, @@ -621,32 +652,6 @@ private Compilation CreateEmptyCompilation() } } - /// - /// Attempts to get (without waiting) a metadata reference to a possibly in progress - /// compilation. Only actual compilation references are returned. Could potentially - /// return null if nothing can be provided. - /// - public MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) - { - if (ProjectState.LanguageServices == fromProject.LanguageServices) - { - // if we have a compilation and its the correct language, use a simple compilation reference in any - // state it happens to be in right now - if (ReadState() is CompilationTrackerState compilationState) - return compilationState.CompilationWithoutGeneratedDocuments.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); - } - else - { - // Cross project reference. We need a skeleton reference. Skeletons are too expensive to - // generate on demand. So just try to see if we can grab the last generated skeleton for that - // project. - var properties = new MetadataReferenceProperties(aliases: projectReference.Aliases, embedInteropTypes: projectReference.EmbedInteropTypes); - return _skeletonReferenceCache.TryGetAlreadyBuiltMetadataReference(properties); - } - - return null; - } - public Task HasSuccessfullyLoadedAsync( SolutionCompilationState compilationState, CancellationToken cancellationToken) { @@ -663,17 +668,79 @@ private async Task HasSuccessfullyLoadedSlowAsync( return finalState.HasSuccessfullyLoaded; } - public ICompilationTracker FreezePartialState(CancellationToken cancellationToken) + public ICompilationTracker WithCreationPolicy(bool create, bool forceRegeneration, CancellationToken cancellationToken) + { + return create + ? WithCreateCreationPolicy(forceRegeneration) + : WithDoNotCreateCreationPolicy(forceRegeneration, cancellationToken); + } + + public ICompilationTracker WithCreateCreationPolicy(bool forceRegeneration) { var state = this.ReadState(); + var desiredCreationPolicy = CreationPolicy.Create; + + // If we've computed no state yet there's nothing to do. This state will automatically transition + // to an InProgressState with a creation policy of 'Create' anyways. + if (state is null) + return this; + + // If we're already in the state where we are running generators and skeletons (and we're not forcing + // regeneration) we don't need to do anything and can just return ourselves. The next request to create + // the compilation will do so fully. + if (state.CreationPolicy == desiredCreationPolicy && !forceRegeneration) + return this; + + // If we're forcing regeneration then we have to drop whatever driver we have so that we'll start from + // scratch next time around. + var desiredGeneratorInfo = forceRegeneration ? state.GeneratorInfo with { Driver = null } : state.GeneratorInfo; + + var newState = state switch + { + InProgressState inProgressState => new InProgressState( + desiredCreationPolicy, + inProgressState.LazyCompilationWithoutGeneratedDocuments, + desiredGeneratorInfo, + inProgressState.LazyStaleCompilationWithGeneratedDocuments, + inProgressState.PendingTranslationActions), + // Transition the final frozen state we have back to an in-progress state that will then compute + // generators and skeletons. + FinalCompilationTrackerState finalState => new InProgressState( + desiredCreationPolicy, + finalState.CompilationWithoutGeneratedDocuments, + desiredGeneratorInfo, + finalState.FinalCompilationWithGeneratedDocuments, + pendingTranslationActions: []), + _ => throw ExceptionUtilities.UnexpectedValue(state.GetType()), + }; + + return new CompilationTracker( + this.ProjectState, + newState, + skeletonReferenceCacheToClone: _skeletonReferenceCache); + } + + public ICompilationTracker WithDoNotCreateCreationPolicy( + bool forceRegeneration, CancellationToken cancellationToken) + { + // We do not expect this to ever be passed true. This is for freezing generators, and no callers + // (currently) will ask to drop drivers when they do that. + Contract.ThrowIfTrue(forceRegeneration); + + var state = this.ReadState(); + + // We're freezing the solution for features where latency performance is paramount. Do not run SGs or + // create skeleton references at this point. Just use whatever we've already generated for each in the + // past. + var desiredCreationPolicy = CreationPolicy.DoNotCreate; + if (state is FinalCompilationTrackerState finalState) { - // If we're finalized and already frozen, we can just use ourselves. Otherwise, flip the frozen bit - // so that any future forks keep things frozen. - return finalState.IsFrozen + var newFinalState = finalState.WithCreationPolicy(desiredCreationPolicy); + return newFinalState == finalState ? this - : new CompilationTracker(this.ProjectState, finalState.WithIsFrozen(), skeletonReferenceCacheToClone: _skeletonReferenceCache); + : new CompilationTracker(this.ProjectState, newFinalState, skeletonReferenceCacheToClone: _skeletonReferenceCache); } // Non-final state currently. Produce an in-progress-state containing the forked change. Note: we @@ -687,7 +754,7 @@ public ICompilationTracker FreezePartialState(CancellationToken cancellationToke // parsed documents over to the new project state so we can preserve as much information as // possible. - // Note: this count may be innacurate as parsing may be going on in the background. However, it + // Note: this count may be inaccurate as parsing may be going on in the background. However, it // acts as a reasonable lower bound for the number of documents we'll be adding. var alreadyParsedCount = this.ProjectState.DocumentStates.States.Count(static s => s.Value.TryGetSyntaxTree(out _)); @@ -722,7 +789,7 @@ public ICompilationTracker FreezePartialState(CancellationToken cancellationToke return new CompilationTracker( frozenProjectState, new InProgressState( - isFrozen: true, + desiredCreationPolicy, lazyCompilationWithoutGeneratedDocuments, CompilationTrackerGeneratorInfo.Empty, lazyCompilationWithGeneratedDocuments, @@ -748,7 +815,7 @@ public ICompilationTracker FreezePartialState(CancellationToken cancellationToke return new CompilationTracker( frozenProjectState, new InProgressState( - isFrozen: true, + desiredCreationPolicy, compilationWithoutGeneratedDocuments, generatorInfo, compilationWithGeneratedDocuments, @@ -765,10 +832,8 @@ public async ValueTask> GetSour SolutionCompilationState compilationState, 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()) - { + if (!await compilationState.HasSourceGeneratorsAsync(this.ProjectState.Id, cancellationToken).ConfigureAwait(false)) return TextDocumentStates.Empty; - } var finalState = await GetOrBuildFinalStateAsync( compilationState, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -778,10 +843,8 @@ public async ValueTask> GetSour public async ValueTask> GetSourceGeneratorDiagnosticsAsync( SolutionCompilationState compilationState, CancellationToken cancellationToken) { - if (!this.ProjectState.SourceGenerators.Any()) - { + if (!await compilationState.HasSourceGeneratorsAsync(this.ProjectState.Id, cancellationToken).ConfigureAwait(false)) return []; - } var finalState = await GetOrBuildFinalStateAsync( compilationState, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -807,10 +870,8 @@ public async ValueTask> GetSourceGeneratorDiagnostics public async ValueTask GetSourceGeneratorRunResultAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) { - if (!this.ProjectState.SourceGenerators.Any()) - { + if (!await compilationState.HasSourceGeneratorsAsync(this.ProjectState.Id, cancellationToken).ConfigureAwait(false)) return null; - } var finalState = await GetOrBuildFinalStateAsync( compilationState, cancellationToken).ConfigureAwait(false); @@ -937,7 +998,7 @@ private static void ValidateCompilationTreesMatchesProjectState(Compilation comp } /// - /// This is just the same as but throws a custom exception type to make this easier to find in telemetry since the exception type + /// This is just the same as but throws a custom exception type to make this easier to find in telemetry since the exception type /// is easily seen in telemetry. /// private static void ThrowExceptionIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs index 73fbee3c5a223..4aea9e09b1162 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.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 System.Diagnostics; using System.Linq; @@ -26,15 +27,15 @@ internal partial class SolutionCompilationState { private partial class CompilationTracker : ICompilationTracker { - private async Task<(Compilation compilationWithGeneratedFiles, TextDocumentStates generatedDocuments, GeneratorDriver? generatorDriver)> AddExistingOrComputeNewGeneratorInfoAsync( - bool isFrozen, + private async Task<(Compilation compilationWithGeneratedFiles, CompilationTrackerGeneratorInfo nextGeneratorInfo)> AddExistingOrComputeNewGeneratorInfoAsync( + CreationPolicy creationPolicy, SolutionCompilationState compilationState, Compilation compilationWithoutGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo, Compilation? compilationWithStaleGeneratedTrees, CancellationToken cancellationToken) { - if (isFrozen) + if (creationPolicy.GeneratedDocumentCreationPolicy is GeneratedDocumentCreationPolicy.DoNotCreate) { // We're frozen. So we do not want to go through the expensive cost of running generators. Instead, we // just whatever prior generated docs we have. @@ -42,54 +43,34 @@ private partial class CompilationTracker : ICompilationTracker static (state, cancellationToken) => state.GetSyntaxTreeAsync(cancellationToken), cancellationToken).ConfigureAwait(false); var compilationWithGeneratedFiles = compilationWithoutGeneratedFiles.AddSyntaxTrees(generatedSyntaxTrees); - return (compilationWithGeneratedFiles, generatorInfo.Documents, generatorInfo.Driver); - } - if (!this.ProjectState.SourceGenerators.Any()) - { - // We don't have any source generators. Trivially bail out. - var compilationWithGeneratedFiles = compilationWithoutGeneratedFiles; - return (compilationWithGeneratedFiles, TextDocumentStates.Empty, generatorInfo.Driver); + // Return the old generator info as is. + return (compilationWithGeneratedFiles, generatorInfo); } - - return await ComputeNewGeneratorInfoAsync( - compilationState, - compilationWithoutGeneratedFiles, - generatorInfo.Documents, - generatorInfo.Driver, - compilationWithStaleGeneratedTrees, - cancellationToken).ConfigureAwait(false); - } - - private async Task<(Compilation compilationWithGeneratedFiles, TextDocumentStates generatedDocuments, GeneratorDriver? generatorDriver)> ComputeNewGeneratorInfoAsync( - SolutionCompilationState compilationState, - Compilation compilationWithoutGeneratedFiles, - TextDocumentStates oldGeneratedDocuments, - GeneratorDriver? oldGeneratorDriver, - Compilation? compilationWithStaleGeneratedTrees, - CancellationToken cancellationToken) - { - // First try to compute the SG docs in the remote process (if we're the host process), syncing the results - // back over to us to ensure that both processes are in total agreement about the SG docs and their - // contents. - var result = await TryComputeNewGeneratorInfoInRemoteProcessAsync( - compilationState, compilationWithoutGeneratedFiles, oldGeneratedDocuments, compilationWithStaleGeneratedTrees, cancellationToken).ConfigureAwait(false); - if (result.HasValue) + else { - // Since we ran the SG work out of process, we could not have created or modified the driver passed in. - // So just pass what we got in right back out. - return (result.Value.compilationWithGeneratedFiles, result.Value.generatedDocuments, oldGeneratorDriver); - } + // First try to compute the SG docs in the remote process (if we're the host process), syncing the results + // back over to us to ensure that both processes are in total agreement about the SG docs and their + // contents. + var result = await TryComputeNewGeneratorInfoInRemoteProcessAsync( + compilationState, compilationWithoutGeneratedFiles, generatorInfo.Documents, compilationWithStaleGeneratedTrees, cancellationToken).ConfigureAwait(false); + if (result.HasValue) + { + // Since we ran the SG work out of process, we could not have created or modified the driver passed in. + // Just return `null` for the driver as there's nothing to track for it on the host side. + return (result.Value.compilationWithGeneratedFiles, new(result.Value.generatedDocuments, Driver: null)); + } - // If that failed (OOP crash, or we are the OOP process ourselves), then generate the SG docs locally. - var telemetryCollector = compilationState.SolutionState.Services.GetService(); - return await ComputeNewGeneratorInfoInCurrentProcessAsync( - telemetryCollector, - compilationWithoutGeneratedFiles, - oldGeneratedDocuments, - oldGeneratorDriver, - compilationWithStaleGeneratedTrees, - cancellationToken).ConfigureAwait(false); + // If that failed (OOP crash, or we are the OOP process ourselves), then generate the SG docs locally. + var (compilationWithGeneratedFiles, nextGeneratedDocuments, nextGeneratorDriver) = await ComputeNewGeneratorInfoInCurrentProcessAsync( + compilationState, + compilationWithoutGeneratedFiles, + generatorInfo.Documents, + generatorInfo.Driver, + compilationWithStaleGeneratedTrees, + cancellationToken).ConfigureAwait(false); + return (compilationWithGeneratedFiles, new(nextGeneratedDocuments, nextGeneratorDriver)); + } } private async Task<(Compilation compilationWithGeneratedFiles, TextDocumentStates generatedDocuments)?> TryComputeNewGeneratorInfoInRemoteProcessAsync( @@ -108,9 +89,8 @@ private partial class CompilationTracker : ICompilationTracker // We're going to be making multiple calls over to OOP. No point in resyncing data multiple times. Keep a // single connection, and keep this solution instance alive (and synced) on both sides of the connection // throughout the calls. - var listenerProvider = solution.Services.ExportProvider.GetExports().First().Value; using var connection = client.CreateConnection(callbackTarget: null); - using var _ = RemoteKeepAliveSession.Create(compilationState, listenerProvider.GetListener(FeatureAttribute.Workspace)); + using var _ = await RemoteKeepAliveSession.CreateAsync(compilationState, cancellationToken).ConfigureAwait(false); // First, grab the info from our external host about the generated documents it has for this project. var projectId = this.ProjectState.Id; @@ -123,13 +103,18 @@ private partial class CompilationTracker : ICompilationTracker if (!infosOpt.HasValue) return null; + var infos = infosOpt.Value; + + // If there are no generated documents, bail out immediately. + if (infos.Length == 0) + return (compilationWithoutGeneratedFiles, TextDocumentStates.Empty); + // Next, figure out what is different locally. Specifically, what documents we don't know about, or we // know about but whose text contents are different. using var _1 = ArrayBuilder.GetInstance(out var documentsToAddOrUpdate); using var _2 = PooledDictionary.GetInstance(out var documentIdToIndex); - var infos = infosOpt.Value; - foreach (var (documentIdentity, contentIdentity) in infos) + foreach (var (documentIdentity, contentIdentity, _) in infos) { var documentId = documentIdentity.DocumentId; Contract.ThrowIfFalse(documentId.IsSourceGenerated); @@ -155,6 +140,13 @@ private partial class CompilationTracker : ICompilationTracker compilationWithStaleGeneratedTrees != null && oldGeneratedDocuments.States.All(kvp => kvp.Value.ParseOptions.Equals(this.ProjectState.ParseOptions))) { + // Even though non of the contents changed, it's possible that the timestamps on them did. + foreach (var (documentIdentity, _, generationDateTime) in infos) + { + var documentId = documentIdentity.DocumentId; + oldGeneratedDocuments = oldGeneratedDocuments.SetState(documentId, oldGeneratedDocuments.GetRequiredState(documentId).WithGenerationDateTime(generationDateTime)); + } + // If there are no generated documents though, then just use the compilationWithoutGeneratedFiles so we // only hold onto that single compilation from this point on. return oldGeneratedDocuments.Count == 0 @@ -180,7 +172,7 @@ private partial class CompilationTracker : ICompilationTracker // Now go through and produce the new document states, using what we have already if it is unchanged, or // what we have retrieved for anything new/changed. using var generatedDocumentsBuilder = TemporaryArray.Empty; - foreach (var (documentIdentity, contentIdentity) in infos) + foreach (var (documentIdentity, contentIdentity, generationDateTime) in infos) { var documentId = documentIdentity.DocumentId; Contract.ThrowIfFalse(documentId.IsSourceGenerated); @@ -200,7 +192,8 @@ private partial class CompilationTracker : ICompilationTracker // Server provided us the checksum, so we just pass that along. Note: it is critical that we do // this as it may not be possible to reconstruct the same checksum the server produced due to // the lossy nature of source texts. See comment on GetOriginalSourceTextChecksum for more detail. - contentIdentity.OriginalSourceTextContentHash); + contentIdentity.OriginalSourceTextContentHash, + generationDateTime); Contract.ThrowIfTrue(generatedDocument.GetOriginalSourceTextContentHash() != contentIdentity.OriginalSourceTextContentHash, "Checksums must match!"); generatedDocumentsBuilder.Add(generatedDocument); } @@ -213,7 +206,9 @@ private partial class CompilationTracker : ICompilationTracker // ParseOptions may have changed between last generation and this one. Ensure that they are // properly propagated to the generated doc. - generatedDocumentsBuilder.Add(existingDocument.WithParseOptions(this.ProjectState.ParseOptions!)); + generatedDocumentsBuilder.Add(existingDocument + .WithParseOptions(this.ProjectState.ParseOptions!) + .WithGenerationDateTime(generationDateTime)); } } @@ -225,13 +220,17 @@ await newGeneratedDocuments.States.Values.SelectAsArrayAsync( } private async Task<(Compilation compilationWithGeneratedFiles, TextDocumentStates generatedDocuments, GeneratorDriver? generatorDriver)> ComputeNewGeneratorInfoInCurrentProcessAsync( - ISourceGeneratorTelemetryCollectorWorkspaceService? telemetryCollector, + SolutionCompilationState compilationState, Compilation compilationWithoutGeneratedFiles, TextDocumentStates oldGeneratedDocuments, GeneratorDriver? generatorDriver, Compilation? compilationWithStaleGeneratedTrees, CancellationToken cancellationToken) { + // If we don't have any source generators. Trivially bail out. + if (!await compilationState.HasSourceGeneratorsAsync(this.ProjectState.Id, cancellationToken).ConfigureAwait(false)) + return (compilationWithoutGeneratedFiles, TextDocumentStates.Empty, generatorDriver); + // If we don't already have an existing generator driver, create one from scratch generatorDriver ??= CreateGeneratorDriver(this.ProjectState); @@ -264,7 +263,10 @@ await newGeneratedDocuments.States.Values.SelectAsArrayAsync( var runResult = generatorDriver.GetRunResult(); - telemetryCollector?.CollectRunResult(runResult, generatorDriver.GetTimingInfo(), ProjectState); + var telemetryCollector = compilationState.SolutionState.Services.GetService(); + telemetryCollector?.CollectRunResult( + runResult, generatorDriver.GetTimingInfo(), + g => GetAnalyzerReference(this.ProjectState, g)); // 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 @@ -281,12 +283,15 @@ await newGeneratedDocuments.States.Values.SelectAsArrayAsync( } using var generatedDocumentsBuilder = TemporaryArray.Empty; + + // Capture the date now. We want all the generated files to use this date consistently. + var generationDateTime = DateTime.Now; foreach (var generatorResult in runResult.Results) { if (IsGeneratorRunResultToIgnore(generatorResult)) continue; - var generatorAnalyzerReference = this.ProjectState.GetAnalyzerReferenceForGenerator(generatorResult.Generator); + var generatorAnalyzerReference = GetAnalyzerReference(this.ProjectState, generatorResult.Generator); foreach (var generatedSource in generatorResult.GeneratedSources) { @@ -302,10 +307,15 @@ await newGeneratedDocuments.States.Values.SelectAsArrayAsync( .WithText(generatedSource.SourceText) .WithParseOptions(this.ProjectState.ParseOptions!); - generatedDocumentsBuilder.Add(newDocument); - + // If changing the text/parse-options actually produced something new, then we can't use the + // stale trees. We also want to mark this point at the point when the document was generated. if (newDocument != existing) + { compilationWithStaleGeneratedTrees = null; + newDocument = newDocument.WithGenerationDateTime(generationDateTime); + } + + generatedDocumentsBuilder.Add(newDocument); } else { @@ -325,7 +335,8 @@ await newGeneratedDocuments.States.Values.SelectAsArrayAsync( generatedSource.SyntaxTree.Options, ProjectState.LanguageServices, // Compute the checksum on demand from the given source text. - originalSourceTextChecksum: null)); + originalSourceTextChecksum: null, + generationDateTime)); // 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 @@ -381,7 +392,7 @@ static GeneratorDriver CreateGeneratorDriver(ProjectState projectState) return compilationFactory.CreateGeneratorDriver( projectState.ParseOptions!, - projectState.SourceGenerators.ToImmutableArray(), + GetSourceGenerators(projectState), projectState.AnalyzerOptions.AnalyzerConfigOptionsProvider, additionalTexts); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CreationPolicy.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CreationPolicy.cs new file mode 100644 index 0000000000000..ff61c5044a6a4 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CreationPolicy.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more 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 partial class SolutionCompilationState +{ + /// + /// Flags controlling if generator documents should be created or not. + /// + private enum GeneratedDocumentCreationPolicy + { + /// + /// Source generators should be run and should produce up to date results. + /// + Create, + + /// + /// Source generators should not run. Whatever results were previously computed should be reused. + /// + DoNotCreate, + } + + /// + /// Flags controlling if skeleton references should be created or not. + /// + private enum SkeletonReferenceCreationPolicy + { + /// + /// Skeleton references should be created, and should be up to date with the project they are created for. + /// + Create, + + /// + /// Skeleton references should only be created for a compilation if no existing skeleton exists for their + /// project from some point in the past. + /// + CreateIfAbsent, + + /// + /// Skeleton references should not be created at all. + /// + DoNotCreate, + } + + private readonly record struct CreationPolicy( + GeneratedDocumentCreationPolicy GeneratedDocumentCreationPolicy, + SkeletonReferenceCreationPolicy SkeletonReferenceCreationPolicy) + { + /// + /// Create up to date source generator docs and create up to date skeleton references when needed. + /// + public static readonly CreationPolicy Create = new(GeneratedDocumentCreationPolicy.Create, SkeletonReferenceCreationPolicy.Create); + + /// + /// Do not create up to date source generator docs and do not create up to date skeleton references for P2P + /// references. For both, use whatever has been generated most recently. + /// + public static readonly CreationPolicy DoNotCreate = new(GeneratedDocumentCreationPolicy.DoNotCreate, SkeletonReferenceCreationPolicy.DoNotCreate); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index 2226a1e71d4eb..d59b89fe1bcc9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs @@ -50,18 +50,21 @@ public GeneratedFileReplacingCompilationTracker( _skeletonReferenceCache = underlyingTracker.GetClonedSkeletonReferenceCache(); } - public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough) + public bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough) { if (_compilationWithReplacements == null) { // We don't have a compilation yet, so this couldn't have came from us + compilation = null; referencedThrough = null; return false; } - else - { - return UnrootedSymbolSet.Create(_compilationWithReplacements).ContainsAssemblyOrModuleOrDynamic(symbol, primary, out referencedThrough); - } + + return RootedSymbolSet.Create(_compilationWithReplacements).ContainsAssemblyOrModuleOrDynamic( + symbol, primary, out compilation, out referencedThrough); } public ICompilationTracker Fork(ProjectState newProject, TranslationAction? translate) @@ -72,10 +75,12 @@ public ICompilationTracker Fork(ProjectState newProject, TranslationAction? tran throw new NotImplementedException(); } - public ICompilationTracker FreezePartialState(CancellationToken cancellationToken) + public ICompilationTracker WithCreationPolicy(bool create, bool forceRegeneration, CancellationToken cancellationToken) { - // Ensure the underlying tracker is totally frozen, and then ensure our replaced generated doc is present. - return new GeneratedFileReplacingCompilationTracker(UnderlyingTracker.FreezePartialState(cancellationToken), _replacementDocumentStates); + var underlyingTracker = this.UnderlyingTracker.WithCreationPolicy(create, forceRegeneration, cancellationToken); + return underlyingTracker == this.UnderlyingTracker + ? this + : new GeneratedFileReplacingCompilationTracker(underlyingTracker, _replacementDocumentStates); } public async Task GetCompilationAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) @@ -143,19 +148,7 @@ public Task GetDependentChecksumAsync(SolutionCompilationState compila private async Task ComputeDependentChecksumAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) => Checksum.Create( await UnderlyingTracker.GetDependentChecksumAsync(compilationState, cancellationToken).ConfigureAwait(false), - (await _replacementDocumentStates.GetChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false)).Checksum); - - public MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) - { - // This method is used if you're forking a solution with partial semantics, and used to quickly produce references. - // So this method should only be called if: - // - // 1. Project A has a open source generated document, and this CompilationTracker represents A - // 2. Project B references that A, and is being frozen for partial semantics. - // - // We generally don't use partial semantics in a different project than the open file, so this isn't a scenario we need to support. - throw new NotImplementedException(); - } + (await _replacementDocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false)).Checksum); public async ValueTask> GetSourceGeneratedDocumentStatesAsync( SolutionCompilationState compilationState, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs index 447d20be70e75..80ef8db9493ac 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs @@ -30,19 +30,28 @@ private interface ICompilationTracker /// of the symbols returned by for /// any of the references of the . /// - bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough); + bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough); ICompilationTracker Fork(ProjectState newProject, TranslationAction? translate); Task GetCompilationAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); - ICompilationTracker FreezePartialState(CancellationToken cancellationToken); + /// + /// Updates the creation policy for this tracker. A value of will set this to and will set it to . + /// + /// When switching to , this will force source + /// generated documents to be created. + ICompilationTracker WithCreationPolicy(bool create, bool forceRegeneration, CancellationToken cancellationToken); Task GetDependentVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); Task GetDependentSemanticVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); Task GetDependentChecksumAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); - MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference); ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); ValueTask> GetSourceGeneratorDiagnosticsAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); ValueTask GetSourceGeneratorRunResultAsync(SolutionCompilationState solution, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs new file mode 100644 index 0000000000000..c33523099f78e --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.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.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.PooledObjects; +using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; + +namespace Microsoft.CodeAnalysis; + +using SecondaryReferencedSymbol = (int hashCode, ISymbol symbol, SolutionCompilationState.MetadataReferenceInfo referenceInfo); + +internal partial class SolutionCompilationState +{ + internal readonly record struct MetadataReferenceInfo(MetadataReferenceProperties Properties, string? FilePath) + { + internal static MetadataReferenceInfo From(MetadataReference reference) + => new(reference.Properties, (reference as PortableExecutableReference)?.FilePath); + } + + /// + /// Information maintained for unrooted symbols. + /// + /// + /// The project the symbol originated from, i.e. the symbol is defined in the project or its metadata reference. + /// + /// + /// The Compilation that produced the symbol. + /// + /// + /// If the symbol is defined in a metadata reference of , information about the + /// reference. + /// + internal sealed record class OriginatingProjectInfo( + ProjectId ProjectId, + Compilation? Compilation, + MetadataReferenceInfo? ReferencedThrough); + + /// + /// A helper type for mapping back to an originating /. + /// + /// + /// In IDE scenarios we have the need to map from an to the that + /// contained a that could have produced that symbol. This is especially needed with OOP + /// scenarios where we have to communicate to OOP from VS (And vice versa) what symbol we are referring to. To do + /// this, we pass along a project where this symbol could be found, and enough information (a ) to resolve that symbol back in that that . + /// + private readonly struct RootedSymbolSet + { + public readonly Compilation Compilation; + + /// + /// The s or s produced through for all the references exposed by . Sorted by the hash code produced by so that it can be binary searched efficiently. + /// + public readonly ImmutableArray SecondaryReferencedSymbols; + + private RootedSymbolSet( + Compilation compilation, + ImmutableArray secondaryReferencedSymbols) + { + Compilation = compilation; + SecondaryReferencedSymbols = secondaryReferencedSymbols; + } + + public static RootedSymbolSet Create(Compilation compilation) + { + // PERF: Preallocate this array so we don't have to resize it as we're adding assembly symbols. + using var _ = ArrayBuilder.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), symbol, MetadataReferenceInfo.From(reference))); + } + + // 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(static (x, y) => x.hashCode.CompareTo(y.hashCode)); + return new RootedSymbolSet(compilation, secondarySymbols.ToImmutable()); + } + + public bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough) + { + if (primary) + { + if (this.Compilation.Assembly.Equals(symbol)) + { + compilation = this.Compilation; + referencedThrough = null; + return true; + } + + if (this.Compilation.Language == LanguageNames.CSharp && + this.Compilation.DynamicType.Equals(symbol)) + { + compilation = this.Compilation; + referencedThrough = null; + return true; + } + } + 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, static (item, symbolHash) => item.hashCode.CompareTo(symbolHash)); + if (index >= 0) + { + // 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]; + if (cached.symbol.Equals(symbol)) + { + referencedThrough = cached.referenceInfo; + compilation = this.Compilation; + return true; + } + + index++; + } + } + } + + compilation = null; + referencedThrough = null; + return false; + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index f0e1ffc395db2..2fbf3bc719d90 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -218,74 +218,79 @@ public readonly SkeletonReferenceCache Clone() private static SkeletonReferenceSet? CreateSkeletonSet( SolutionServices services, Compilation compilation, CancellationToken cancellationToken) { - var storage = TryCreateMetadataStorage(services, compilation, cancellationToken); - if (storage == null) + var (metadata, storageHandle) = TryCreateMetadataAndHandle(); + if (metadata == null) return null; - var metadata = AssemblyMetadata.CreateFromStream(storage.ReadStream(cancellationToken), leaveOpen: false); - // read in the stream and pass ownership of it to the metadata object. When it is disposed it will dispose // the stream as well. return new SkeletonReferenceSet( metadata, + storageHandle, compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - } - - private static ITemporaryStreamStorageInternal? TryCreateMetadataStorage(SolutionServices services, Compilation compilation, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var logger = services.GetService(); - try + (AssemblyMetadata? metadata, ITemporaryStorageStreamHandle storageHandle) TryCreateMetadataAndHandle() { - logger?.Log($"Beginning to create a skeleton assembly for {compilation.AssemblyName}..."); + cancellationToken.ThrowIfCancellationRequested(); - using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) - { - using var stream = SerializableBytes.CreateWritableStream(); + var logger = services.GetService(); - var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); + try + { + logger?.Log($"Beginning to create a skeleton assembly for {compilation.AssemblyName}..."); - if (emitResult.Success) + using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) { - logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + // First, emit the data to an in-memory stream. + using var stream = SerializableBytes.CreateWritableStream(); + var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); - var temporaryStorageService = services.GetRequiredService(); - var storage = temporaryStorageService.CreateTemporaryStreamStorage(); + if (emitResult.Success) + { + logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); - stream.Position = 0; - storage.WriteStream(stream, cancellationToken); + var temporaryStorageService = services.GetRequiredService(); - return storage; - } + // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the + // assembly-metadata point directly to that pointer in memory, instead of it having to make its + // own copy it needs to own the lifetime of. + var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); - if (logger != null) - { - logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + // Now read the data back from the stream from the memory mapped file. This will come back as an + // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. + var result = AssemblyMetadata.CreateFromStream( + handle.ReadFromTemporaryStorage(cancellationToken), leaveOpen: false); - foreach (var diagnostic in emitResult.Diagnostics) + return (result, handle); + } + + if (logger != null) { - logger.Log(" " + diagnostic.GetMessage()); + logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + + foreach (var diagnostic in emitResult.Diagnostics) + { + logger.Log(" " + diagnostic.GetMessage()); + } } - } - // log emit failures so that we can improve most common cases - Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => - { - // log errors in the format of - // CS0001:1;CS002:10;... - var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); - m["Errors"] = string.Join(";", groups); - })); + // log emit failures so that we can improve most common cases + Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => + { + // log errors in the format of + // CS0001:1;CS002:10;... + var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); + m["Errors"] = string.Join(";", groups); + })); - return null; + return (null, null!); + } + } + finally + { + logger?.Log($"Done trying to create a skeleton assembly for {compilation.AssemblyName}"); } - } - finally - { - logger?.Log($"Done trying to create a skeleton assembly for {compilation.AssemblyName}"); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs index 033c900fecff5..e3e9b84b07744 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis; @@ -18,6 +19,7 @@ internal partial class SolutionCompilationState /// private sealed class SkeletonReferenceSet( AssemblyMetadata metadata, + ITemporaryStorageStreamHandle storageHandle, string? assemblyName, DeferredDocumentationProvider documentationProvider) { @@ -29,6 +31,8 @@ private sealed class SkeletonReferenceSet( /// private readonly Dictionary _referenceMap = []; + public ITemporaryStorageStreamHandle StorageHandle => storageHandle; + public PortableExecutableReference GetOrCreateMetadataReference(MetadataReferenceProperties properties) { lock (_referenceMap) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs index 52a13061db566..5e86a295e2ef4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -172,13 +173,16 @@ internal partial class SolutionCompilationState return projectId; } - else if (symbol.IsKind(SymbolKind.TypeParameter, out ITypeParameterSymbol? typeParameter) && - typeParameter.TypeParameterKind == TypeParameterKind.Cref) + else if (symbol is ITypeParameterSymbol + { + TypeParameterKind: TypeParameterKind.Cref, + Locations: [{ SourceTree: var typeParameterSourceTree }, ..], + }) { // Cref type parameters don't belong to any containing symbol. But we can map them to a doc/project // using the declaring syntax of the type parameter itself. - if (GetDocumentState(typeParameter.Locations[0].SourceTree, projectId: null) is { } document) - return new OriginatingProjectInfo(document.Id.ProjectId, ReferencedThrough: null); + if (GetDocumentState(typeParameterSourceTree, projectId: null) is { } document) + return new OriginatingProjectInfo(document.Id.ProjectId, Compilation: null, ReferencedThrough: null); } return null; @@ -187,8 +191,8 @@ internal partial class SolutionCompilationState { foreach (var (id, tracker) in _projectIdToTrackerMap) { - if (tracker.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out var referencedThrough)) - return new OriginatingProjectInfo(id, referencedThrough); + if (tracker.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out var compilation, out var referencedThrough)) + return new OriginatingProjectInfo(id, compilation, referencedThrough); } return null; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs index 9f15aed0ffec4..30961e91913ed 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs @@ -355,7 +355,7 @@ public override GeneratorDriver TransformGeneratorDriver(GeneratorDriver _) .ReplaceAdditionalTexts(this.NewProjectState.AdditionalDocumentStates.SelectAsArray(static documentState => documentState.AdditionalText)) .WithUpdatedParseOptions(this.NewProjectState.ParseOptions!) .WithUpdatedAnalyzerConfigOptions(this.NewProjectState.AnalyzerOptions.AnalyzerConfigOptionsProvider) - .ReplaceGenerators(this.NewProjectState.SourceGenerators.ToImmutableArray()); + .ReplaceGenerators(GetSourceGenerators(this.NewProjectState)); return generatorDriver; } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs deleted file mode 100644 index 06837aab31a62..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs +++ /dev/null @@ -1,156 +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 Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; - -namespace Microsoft.CodeAnalysis; - -using SecondaryReferencedSymbol = (int hashCode, WeakReference symbol, SolutionCompilationState.MetadataReferenceInfo referenceInfo); - -internal partial class SolutionCompilationState -{ - internal readonly record struct MetadataReferenceInfo(MetadataReferenceProperties Properties, string? FilePath) - { - internal static MetadataReferenceInfo From(MetadataReference reference) - => new(reference.Properties, (reference as PortableExecutableReference)?.FilePath); - } - - /// - /// Information maintained for unrooted symbols. - /// - /// - /// The project the symbol originated from, i.e. the symbol is defined in the project or its metadata reference. - /// - /// - /// If the symbol is defined in a metadata reference of , information about the reference. - /// - internal sealed record class OriginatingProjectInfo(ProjectId ProjectId, MetadataReferenceInfo? ReferencedThrough); - - /// - /// A helper type for mapping back to an originating . - /// - /// - /// In IDE scenarios we have the need to map from an to the that - /// contained a that could have produced that symbol. This is especially needed with - /// OOP scenarios where we have to communicate to OOP from VS (And vice versa) what symbol we are referring to. - /// To do this, we pass along a project where this symbol could be found, and enough information (a ) to resolve that symbol back in that that . - /// - /// This is challenging however as symbols do not necessarily have back-pointers to s, - /// and as such, we can't just see which Project produced the that produced that . In other words, the doesn't root the compilation. Because - /// of that we keep track of those symbols per project in a weak fashion. Then, we can later see if a - /// symbol came from a particular project by checking if it is one of those weak symbols. We use weakly held - /// symbols to that a instance doesn't hold symbols alive. But, we know if we are - /// holding the symbol itself, then the weak-ref will stay alive such that we can do this containment check. - /// - /// - private readonly struct UnrootedSymbolSet - { - /// - /// The produced directly by . - /// - public readonly WeakReference PrimaryAssemblySymbol; - - /// - /// The produced directly by . Only - /// valid for . - /// - public readonly WeakReference PrimaryDynamicSymbol; - - /// - /// The s or s produced through for all the references exposed by . Sorted by the hash code produced by so that it can be binary searched efficiently. - /// - public readonly ImmutableArray SecondaryReferencedSymbols; - - private UnrootedSymbolSet( - WeakReference primaryAssemblySymbol, - WeakReference primaryDynamicSymbol, - ImmutableArray secondaryReferencedSymbols) - { - PrimaryAssemblySymbol = primaryAssemblySymbol; - 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.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), MetadataReferenceInfo.From(reference))); - } - - // 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(static (x, y) => x.hashCode.CompareTo(y.hashCode)); - return new UnrootedSymbolSet(primaryAssembly, primaryDynamic, secondarySymbols.ToImmutable()); - } - - public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough) - { - referencedThrough = null; - - if (primary) - { - return symbol.Equals(this.PrimaryAssemblySymbol.GetTarget()) || - symbol.Equals(this.PrimaryDynamicSymbol.GetTarget()); - } - - 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, static (item, symbolHash) => item.hashCode.CompareTo(symbolHash)); - 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]; - if (cached.symbol.TryGetTarget(out var otherSymbol) && otherSymbol == symbol) - { - referencedThrough = cached.referenceInfo; - return true; - } - - index++; - } - - return false; - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 4f39efd34e3a1..058055eb643fe 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; @@ -46,6 +47,16 @@ internal sealed partial class SolutionCompilationState // Values for all these are created on demand. private ImmutableSegmentedDictionary _projectIdToTrackerMap; + /// + /// Map from each project to the it is currently at. Loosely, the + /// execution version allows us to have the generated documents for a project get fixed at some point in the past + /// when they were generated, up until events happen in the host that cause a need for them to be brought up to + /// date. This is ambient, compilation-level, information about our projects, which is why it is stored at this + /// compilation-state level. When syncing to our OOP process, this information is included, allowing the oop side + /// to move its own generators forward when a host changes these versions. + /// + private readonly SourceGeneratorExecutionVersionMap _sourceGeneratorExecutionVersionMap; + /// /// Cache we use to map between unrooted symbols (i.e. assembly, module and dynamic symbols) and the project /// they came from. That way if we are asked about many symbols from the same assembly/module we can answer the @@ -60,12 +71,14 @@ private SolutionCompilationState( SolutionState solution, bool partialSemanticsEnabled, ImmutableSegmentedDictionary projectIdToTrackerMap, + SourceGeneratorExecutionVersionMap sourceGeneratorExecutionVersionMap, TextDocumentStates? frozenSourceGeneratedDocumentStates, AsyncLazy? cachedFrozenSnapshot = null) { SolutionState = solution; PartialSemanticsEnabled = partialSemanticsEnabled; _projectIdToTrackerMap = projectIdToTrackerMap; + _sourceGeneratorExecutionVersionMap = sourceGeneratorExecutionVersionMap; FrozenSourceGeneratedDocumentStates = frozenSourceGeneratedDocumentStates; // when solution state is changed, we recalculate its checksum @@ -90,6 +103,7 @@ public SolutionCompilationState( solution, partialSemanticsEnabled, projectIdToTrackerMap: ImmutableSegmentedDictionary.Empty, + sourceGeneratorExecutionVersionMap: SourceGeneratorExecutionVersionMap.Empty, frozenSourceGeneratedDocumentStates: null) { } @@ -102,20 +116,29 @@ private void CheckInvariants() { // An id shouldn't point at a tracker for a different project. Contract.ThrowIfTrue(_projectIdToTrackerMap.Any(kvp => kvp.Key != kvp.Value.ProjectState.Id)); + + // Solution and SG version maps must correspond to the same set of projets. + Contract.ThrowIfFalse(this.SolutionState.ProjectStates + .Where(kvp => RemoteSupportedLanguages.IsSupported(kvp.Value.Language)) + .Select(kvp => kvp.Key) + .SetEquals(_sourceGeneratorExecutionVersionMap.Map.Keys)); } private SolutionCompilationState Branch( SolutionState newSolutionState, ImmutableSegmentedDictionary? projectIdToTrackerMap = null, + SourceGeneratorExecutionVersionMap? sourceGeneratorExecutionVersionMap = null, Optional?> frozenSourceGeneratedDocumentStates = default, AsyncLazy? cachedFrozenSnapshot = null) { projectIdToTrackerMap ??= _projectIdToTrackerMap; + sourceGeneratorExecutionVersionMap ??= _sourceGeneratorExecutionVersionMap; var newFrozenSourceGeneratedDocumentStates = frozenSourceGeneratedDocumentStates.HasValue ? frozenSourceGeneratedDocumentStates.Value : FrozenSourceGeneratedDocumentStates; if (newSolutionState == this.SolutionState && projectIdToTrackerMap == _projectIdToTrackerMap && - newFrozenSourceGeneratedDocumentStates.Equals(FrozenSourceGeneratedDocumentStates)) + sourceGeneratorExecutionVersionMap == _sourceGeneratorExecutionVersionMap && + Equals(newFrozenSourceGeneratedDocumentStates, FrozenSourceGeneratedDocumentStates)) { return this; } @@ -124,6 +147,7 @@ private SolutionCompilationState Branch( newSolutionState, PartialSemanticsEnabled, projectIdToTrackerMap.Value, + sourceGeneratorExecutionVersionMap, newFrozenSourceGeneratedDocumentStates, cachedFrozenSnapshot); } @@ -290,34 +314,90 @@ private ImmutableSegmentedDictionary CreateCompi return projectIdToTrackerMapBuilder.ToImmutable(); } - /// - public SolutionCompilationState AddProject(ProjectInfo projectInfo) + public SourceGeneratorExecutionVersionMap SourceGeneratorExecutionVersionMap => _sourceGeneratorExecutionVersionMap; + + /// + public SolutionCompilationState AddProjects(ArrayBuilder projectInfos) { - var newSolutionState = this.SolutionState.AddProject(projectInfo); - var newTrackerMap = CreateCompilationTrackerMap(projectInfo.Id, newSolutionState.GetProjectDependencyGraph(), static (_, _) => { }, /* unused */ 0, skipEmptyCallback: true); + if (projectInfos.Count == 0) + return this; + + var newSolutionState = this.SolutionState.AddProjects(projectInfos); + // When adding a project, we might add a project that an *existing* project now has a reference to. That's + // because we allow existing projects to have 'dangling' project references. As such, we have to ensure we do + // not reuse compilation trackers for any of those projects. + using var _ = PooledHashSet.GetInstance(out var dependentProjects); + var newDependencyGraph = newSolutionState.GetProjectDependencyGraph(); + foreach (var projectInfo in projectInfos) + dependentProjects.AddRange(newDependencyGraph.GetProjectsThatTransitivelyDependOnThisProject(projectInfo.Id)); + + var newTrackerMap = CreateCompilationTrackerMap( + static (projectId, dependentProjects) => !dependentProjects.Contains(projectId), + dependentProjects, + // We don't need to do anything here. Compilation trackers are created on demand. So we'll just keep the + // tracker map as-is, and have the trackers for these new projects be created when needed. + modifyNewTrackerInfo: static (_, _) => { }, argModifyNewTrackerInfo: default(VoidResult), + skipEmptyCallback: true); + + var versionMapBuilder = _sourceGeneratorExecutionVersionMap.Map.ToBuilder(); + foreach (var projectInfo in projectInfos) + { + if (RemoteSupportedLanguages.IsSupported(projectInfo.Language)) + versionMapBuilder.Add(projectInfo.Id, new()); + } + + var sourceGeneratorExecutionVersionMap = new SourceGeneratorExecutionVersionMap(versionMapBuilder.ToImmutable()); return Branch( newSolutionState, - projectIdToTrackerMap: newTrackerMap); + projectIdToTrackerMap: newTrackerMap, + sourceGeneratorExecutionVersionMap: sourceGeneratorExecutionVersionMap); } - /// - public SolutionCompilationState RemoveProject(ProjectId projectId) + /// + public SolutionCompilationState RemoveProjects(ArrayBuilder projectIds) { - var newSolutionState = this.SolutionState.RemoveProject(projectId); + if (projectIds.Count == 0) + return this; + + // Now go and remove the projects from teh solution-state itself. + var newSolutionState = this.SolutionState.RemoveProjects(projectIds); + + var originalDependencyGraph = this.SolutionState.GetProjectDependencyGraph(); + using var _ = PooledHashSet.GetInstance(out var dependentProjects); + + // Determine the set of projects that depend on the projects being removed. + foreach (var projectId in projectIds) + { + foreach (var dependentProject in originalDependencyGraph.GetProjectsThatTransitivelyDependOnThisProject(projectId)) + dependentProjects.Add(dependentProject); + } + + // Now for each compilation tracker. + // 1. remove the compilation tracker if we're removing the project. + // 2. fork teh compilation tracker if it depended on a removed project. + // 3. do nothing for the rest. var newTrackerMap = CreateCompilationTrackerMap( - projectId, - newSolutionState.GetProjectDependencyGraph(), - static (trackerMap, projectId) => + // Can reuse the compilation tracker for a project, unless it is some project that had a dependency on one + // of the projects removed. + static (projectId, dependentProjects) => !dependentProjects.Contains(projectId), + dependentProjects, + static (trackerMap, projectIds) => { - trackerMap.Remove(projectId); + foreach (var projectId in projectIds) + trackerMap.Remove(projectId); }, - projectId, + projectIds, skipEmptyCallback: true); + var versionMapBuilder = _sourceGeneratorExecutionVersionMap.Map.ToBuilder(); + foreach (var projectId in projectIds) + versionMapBuilder.Remove(projectId); + return this.Branch( newSolutionState, - projectIdToTrackerMap: newTrackerMap); + projectIdToTrackerMap: newTrackerMap, + sourceGeneratorExecutionVersionMap: new(versionMapBuilder.ToImmutable())); } /// @@ -929,24 +1009,6 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( return GetCompilationTracker(documentId.ProjectId).TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); } - /// - /// Attempt to get the best readily available compilation for the project. It may be a - /// partially built compilation. - /// - private MetadataReference? GetPartialMetadataReference( - ProjectReference projectReference, - ProjectState fromProject) - { - // Try to get the compilation state for this project. If it doesn't exist, don't do any - // more work. - if (!_projectIdToTrackerMap.TryGetValue(projectReference.ProjectId, out var state)) - { - return null; - } - - return state.GetPartialMetadataReference(fromProject, projectReference); - } - /// /// Get a metadata reference to this compilation info's compilation with respect to /// another project. For cross language references produce a skeletal assembly. If the @@ -954,7 +1016,7 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( /// needed and does not exist, it is also built. /// private async Task GetMetadataReferenceAsync( - ICompilationTracker tracker, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) + ICompilationTracker tracker, ProjectState fromProject, ProjectReference projectReference, bool includeCrossLanguage, CancellationToken cancellationToken) { try { @@ -966,6 +1028,9 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); } + if (!includeCrossLanguage) + return null; + // otherwise get a metadata only image reference that is built by emitting the metadata from the // referenced project's compilation and re-importing it. using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_GetMetadataOnlyImage, cancellationToken)) @@ -985,14 +1050,14 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( /// can happen when trying to build a skeleton reference that fails to build. /// public Task GetMetadataReferenceAsync( - ProjectReference projectReference, ProjectState fromProject, CancellationToken cancellationToken) + ProjectReference projectReference, ProjectState fromProject, bool includeCrossLanguage, CancellationToken cancellationToken) { try { // Get the compilation state for this project. If it's not already created, then this // will create it. Then force that state to completion and get a metadata reference to it. var tracker = this.GetCompilationTracker(projectReference.ProjectId); - return GetMetadataReferenceAsync(tracker, fromProject, projectReference, cancellationToken); + return GetMetadataReferenceAsync(tracker, fromProject, projectReference, includeCrossLanguage, cancellationToken); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) { @@ -1010,7 +1075,7 @@ public SolutionCompilationState WithoutFrozenSourceGeneratedDocuments() if (FrozenSourceGeneratedDocumentStates == null) return this; - var projectIdsToUnfreeze = FrozenSourceGeneratedDocumentStates.Value.States.Values + var projectIdsToUnfreeze = FrozenSourceGeneratedDocumentStates.States.Values .Select(static state => state.Identity.DocumentId.ProjectId) .Distinct() .ToImmutableArray(); @@ -1046,7 +1111,7 @@ public SolutionCompilationState WithoutFrozenSourceGeneratedDocuments() /// generated file open, we need to make sure everything lines up. /// public SolutionCompilationState WithFrozenSourceGeneratedDocuments( - ImmutableArray<(SourceGeneratedDocumentIdentity documentIdentity, SourceText sourceText)> documents) + ImmutableArray<(SourceGeneratedDocumentIdentity documentIdentity, DateTime generationDateTime, SourceText sourceText)> documents) { // We won't support freezing multiple source generated documents more than once in a chain, simply because we have no need // to support that; these solutions are created on demand when we need to operate on an open source generated document, @@ -1060,14 +1125,15 @@ public SolutionCompilationState WithFrozenSourceGeneratedDocuments( // We'll keep track if every document we're reusing is the exact same as the final generated output we already have using var _ = ArrayBuilder.GetInstance(documents.Length, out var documentStates); - foreach (var (documentIdentity, sourceText) in documents) + foreach (var (documentIdentity, generationDateTime, sourceText) in documents) { var existingGeneratedState = TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentIdentity.DocumentId); if (existingGeneratedState != null) { var newGeneratedState = existingGeneratedState .WithText(sourceText) - .WithParseOptions(existingGeneratedState.ParseOptions); + .WithParseOptions(existingGeneratedState.ParseOptions) + .WithGenerationDateTime(generationDateTime); // If the content already matched, we can just reuse the existing state, so we don't need to track this one if (newGeneratedState != existingGeneratedState) @@ -1083,7 +1149,8 @@ public SolutionCompilationState WithFrozenSourceGeneratedDocuments( projectState.ParseOptions!, projectState.LanguageServices, // Just compute the checksum from the source text passed in. - originalSourceTextChecksum: null); + originalSourceTextChecksum: null, + generationDateTime); documentStates.Add(newGeneratedState); } } @@ -1094,7 +1161,7 @@ public SolutionCompilationState WithFrozenSourceGeneratedDocuments( var documentStatesByProjectId = documentStates.ToDictionary(static state => state.Id.ProjectId); var newTrackerMap = CreateCompilationTrackerMap( - documentStatesByProjectId.Keys.ToImmutableArray(), + [.. documentStatesByProjectId.Keys], this.SolutionState.GetProjectDependencyGraph(), static (trackerMap, arg) => { @@ -1134,6 +1201,49 @@ public SolutionCompilationState WithOptions(SolutionOptionSet options) this.SolutionState.WithOptions(options)); } + public SolutionCompilationState WithSourceGeneratorExecutionVersions( + SourceGeneratorExecutionVersionMap sourceGeneratorExecutionVersions, CancellationToken cancellationToken) + { + var versionMapBuilder = _sourceGeneratorExecutionVersionMap.Map.ToBuilder(); + var newIdToTrackerMapBuilder = _projectIdToTrackerMap.ToBuilder(); + var changed = false; + + foreach (var (projectId, sourceGeneratorExecutionVersion) in sourceGeneratorExecutionVersions.Map) + { + cancellationToken.ThrowIfCancellationRequested(); + + var currentExecutionVersion = versionMapBuilder[projectId]; + + // Nothing to do if already at this version. + if (currentExecutionVersion == sourceGeneratorExecutionVersion) + continue; + + changed = true; + versionMapBuilder[projectId] = sourceGeneratorExecutionVersion; + + // If we do already have a compilation tracker for this project, then let the tracker know that the source + // generator version has changed. We do this by telling it that it should now create SG docs and skeleton + // references if they're out of date. + if (_projectIdToTrackerMap.TryGetValue(projectId, out var existingTracker)) + { + // if the major version has changed then we also want to drop the generator driver so that we're rerun + // generators from scratch. + var forceRegeneration = currentExecutionVersion.MajorVersion != sourceGeneratorExecutionVersion.MajorVersion; + var newTracker = existingTracker.WithCreationPolicy(create: true, forceRegeneration, cancellationToken); + if (newTracker != existingTracker) + newIdToTrackerMapBuilder[projectId] = newTracker; + } + } + + if (!changed) + return this; + + return this.Branch( + this.SolutionState, + projectIdToTrackerMap: newIdToTrackerMapBuilder.ToImmutable(), + sourceGeneratorExecutionVersionMap: new(versionMapBuilder.ToImmutable())); + } + public SolutionCompilationState WithFrozenPartialCompilations(CancellationToken cancellationToken) => _cachedFrozenSnapshot.GetValue(cancellationToken); @@ -1142,19 +1252,6 @@ private SolutionCompilationState ComputeFrozenSnapshot(CancellationToken cancell var newIdToProjectStateMapBuilder = this.SolutionState.ProjectStates.ToBuilder(); var newIdToTrackerMapBuilder = _projectIdToTrackerMap.ToBuilder(); - // Keep track of the files that were potentially added between the last frozen snapshot point we have for a - // project and now. Specifically, if a file was removed in reality, it may show us as an add as we are - // effectively jumping back to a prior point in time for a particular project. We want all those files (and - // related doc ids) to be present in the frozen solution we hand back. - // - // Note: we only keep track of added files. We do not keep track of removed files. This is intentionally done - // for performance reasons. Specifically, it is quite normal for a project to drop all documents when frozen - // (for example, when no documents have been parsed in it). Actually dropping all these files from this map is - // very expensive. This does mean that the FilePathToDocumentIdsMap will be a superset of all files. That's - // ok. We'll mark this map as being frozen (and thus potentially containing a superset of legal ids), and later - // on our helpers will check for that and filter down to the set that is in a solution when queried. - var filePathToDocumentIdsMapBuilder = this.SolutionState.FilePathToDocumentIdsMap.ToFrozen().ToBuilder(); - foreach (var projectId in this.SolutionState.ProjectIds) { cancellationToken.ThrowIfCancellationRequested(); @@ -1165,7 +1262,10 @@ private SolutionCompilationState ComputeFrozenSnapshot(CancellationToken cancell continue; var oldTracker = GetCompilationTracker(projectId); - var newTracker = oldTracker.FreezePartialState(cancellationToken); + + // Since we're freezing, set both generators and skeletons to not be created. We don't want to take any + // perf hit on either of those at all for our clients. + var newTracker = oldTracker.WithCreationPolicy(create: false, forceRegeneration: false, cancellationToken); if (oldTracker == newTracker) continue; @@ -1175,39 +1275,15 @@ private SolutionCompilationState ComputeFrozenSnapshot(CancellationToken cancell newIdToProjectStateMapBuilder[projectId] = newProjectState; newIdToTrackerMapBuilder[projectId] = newTracker; - - // Freezing projects can cause them to have an entirely different set of documents (since it effectively - // rewinds the project back to the last time it produced a compilation). Ensure we keep track of the docs - // added or removed from the project states to keep the final filepath-to-documentid map accurate. - // - // Note: we only have to do this if the actual project-state changed. If we were able to use the same - // instance (common if we already got the compilation for a project), then nothing changes with the set - // of documents. - // - // Examples of where the documents may absolutely change though are when we haven't even gotten a - // compilation yet. In that case, the project transitions to an empty state, which means we should remove - // all its documents from the filePathToDocumentIdsMap. Similarly, if we were at some in-progress-state we - // might reset the project back to a prior state from when the last compilation was requested, losing - // information about documents recently added or removed. - - if (oldProjectState != newProjectState) - { - AddMissingOrChangedFilePathMappings(filePathToDocumentIdsMapBuilder, oldProjectState.DocumentStates, newProjectState.DocumentStates); - AddMissingOrChangedFilePathMappings(filePathToDocumentIdsMapBuilder, oldProjectState.AdditionalDocumentStates, newProjectState.AdditionalDocumentStates); - AddMissingOrChangedFilePathMappings(filePathToDocumentIdsMapBuilder, oldProjectState.AnalyzerConfigDocumentStates, newProjectState.AnalyzerConfigDocumentStates); - } } var newIdToProjectStateMap = newIdToProjectStateMapBuilder.ToImmutable(); var newIdToTrackerMap = newIdToTrackerMapBuilder.ToImmutable(); - var filePathToDocumentIdsMap = filePathToDocumentIdsMapBuilder.ToImmutable(); - var dependencyGraph = SolutionState.CreateDependencyGraph(this.SolutionState.ProjectIds, newIdToProjectStateMap); var newState = this.SolutionState.Branch( idToProjectStateMap: newIdToProjectStateMap, - filePathToDocumentIdsMap: filePathToDocumentIdsMap, dependencyGraph: dependencyGraph); var newCompilationState = this.Branch( @@ -1217,41 +1293,6 @@ private SolutionCompilationState ComputeFrozenSnapshot(CancellationToken cancell cachedFrozenSnapshot: _cachedFrozenSnapshot); return newCompilationState; - - static void AddMissingOrChangedFilePathMappings( - FilePathToDocumentIdsMap.Builder filePathToDocumentIdsMapBuilder, - TextDocumentStates oldStates, - TextDocumentStates newStates) where TDocumentState : TextDocumentState - { - if (oldStates.Equals(newStates)) - return; - - // We want to make sure that all the documents in the new-state are properly represented in the file map. - // It's ok if old-state documents are still in the map as GetDocumentIdsWithFilePath will filter them out - // later since we're producing a frozen-partial map. - // - // Iterating over the new-states has an additional benefit. For projects that haven't ever been looked at - // (so they haven't really parsed any documents), this will results in empty new-states. So this loop will - // be almost a no-op for most non-relevant projects. - foreach (var (documentId, newDocumentState) in newStates.States) - { - if (!oldStates.TryGetState(documentId, out var oldDocumentState)) - { - // Keep track of files that are definitely added. Make sure the added doc is in the file path map. - filePathToDocumentIdsMapBuilder.Add(newDocumentState.FilePath, documentId); - - } - else if (oldDocumentState != newDocumentState && - oldDocumentState.FilePath != newDocumentState.FilePath) - { - // Otherwise, if the document is in both, but the file name changed, then remove the old mapping - // and add the new mapping. Importantly, we don't want other linked files with the *old* path - // to consider this document one of their linked brethren. - filePathToDocumentIdsMapBuilder.Remove(oldDocumentState.FilePath, oldDocumentState.Id); - filePathToDocumentIdsMapBuilder.Add(newDocumentState.FilePath, newDocumentState.Id); - } - } - } } /// @@ -1467,9 +1508,7 @@ private SolutionCompilationState AddDocumentsToMultipleProjects( var stateChange = newCompilationState.SolutionState.ForkProject( oldProjectState, - newProjectState, - // intentionally accessing this.Solution here not newSolutionState - newFilePathToDocumentIdsMap: this.SolutionState.CreateFilePathToDocumentIdsMapWithAddedDocuments(newDocumentStates)); + newProjectState); newCompilationState = newCompilationState.ForkProject( stateChange, @@ -1516,14 +1555,12 @@ private SolutionCompilationState RemoveDocumentsFromMultipleProjects( var removedDocumentStatesForProject = removedDocumentStates.ToImmutable(); - var compilationTranslationAction = removeDocumentsFromProjectState(oldProjectState, documentIdsInProject.ToImmutableArray(), removedDocumentStatesForProject); + var compilationTranslationAction = removeDocumentsFromProjectState(oldProjectState, [.. documentIdsInProject], removedDocumentStatesForProject); var newProjectState = compilationTranslationAction.NewProjectState; var stateChange = newCompilationState.SolutionState.ForkProject( oldProjectState, - newProjectState, - // Intentionally using this.Solution here and not newSolutionState - newFilePathToDocumentIdsMap: this.SolutionState.CreateFilePathToDocumentIdsMapWithRemovedDocuments(removedDocumentStatesForProject)); + newProjectState); newCompilationState = newCompilationState.ForkProject( stateChange, diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index 830d52cd72c3d..82600b5d6b1da 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -4,11 +4,15 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; @@ -122,21 +126,31 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo } ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; - ChecksumsAndIds? frozenSourceGeneratedDocuments = null; + DocumentChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; + ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes = default; - if (FrozenSourceGeneratedDocumentStates.HasValue) + if (FrozenSourceGeneratedDocumentStates != null) { var serializer = this.SolutionState.Services.GetRequiredService(); - var identityChecksums = FrozenSourceGeneratedDocumentStates.Value - .SelectAsArray(static (s, arg) => arg.serializer.CreateChecksum(s.Identity, cancellationToken: arg.cancellationToken), (serializer, cancellationToken)); + var identityChecksums = FrozenSourceGeneratedDocumentStates.SelectAsArray( + static (s, arg) => arg.serializer.CreateChecksum(s.Identity, cancellationToken: arg.cancellationToken), (serializer, cancellationToken)); + + frozenSourceGeneratedDocumentTexts = await FrozenSourceGeneratedDocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); frozenSourceGeneratedDocumentIdentities = new ChecksumCollection(identityChecksums); - frozenSourceGeneratedDocuments = await FrozenSourceGeneratedDocumentStates.Value.GetChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); + frozenSourceGeneratedDocumentGenerationDateTimes = FrozenSourceGeneratedDocumentStates.SelectAsArray(d => d.GenerationDateTime); } + var versionMapChecksum = ChecksumCache.GetOrCreate( + this.SourceGeneratorExecutionVersionMap, + static (map, @this) => GetVersionMapChecksum(@this), + this); + var compilationStateChecksums = new SolutionCompilationStateChecksums( solutionStateChecksum, + versionMapChecksum, + frozenSourceGeneratedDocumentTexts, frozenSourceGeneratedDocumentIdentities, - frozenSourceGeneratedDocuments); + frozenSourceGeneratedDocumentGenerationDateTimes); return (compilationStateChecksums, projectCone); } } @@ -144,5 +158,30 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo { throw ExceptionUtilities.Unreachable(); } + + static Checksum GetVersionMapChecksum(SolutionCompilationState @this) + { + // We want the projects in sorted order so we can generate the checksum for the + // source-generation-execution-map consistently. + var sortedProjectIds = SolutionState.GetOrCreateSortedProjectIds(@this.SolutionState.ProjectIds); + var supportedCount = sortedProjectIds.Count( + static (projectId, @this) => RemoteSupportedLanguages.IsSupported(@this.SolutionState.GetRequiredProjectState(projectId).Language), + @this); + + // For each project, we'll add one checksum for the project id and one for the version map. + using var _ = ArrayBuilder.GetInstance(2 * supportedCount, out var checksums); + + foreach (var projectId in sortedProjectIds) + { + var projectState = @this.SolutionState.GetRequiredProjectState(projectId); + if (!RemoteSupportedLanguages.IsSupported(projectState.Language)) + continue; + + checksums.Add(projectId.Checksum); + checksums.Add(Checksum.Create(@this.SourceGeneratorExecutionVersionMap[projectId], static (v, w) => v.WriteTo(w))); + } + + return Checksum.Create(checksums); + } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs new file mode 100644 index 0000000000000..4a94680e0bd91 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more 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.Frozen; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Collections; +using Microsoft.CodeAnalysis.SourceGeneration; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis; + +// Cache of list of analyzer references to whether or not they have source generators. Keyed based off +// IReadOnlyList so that we can cache the value even as project-states fork based on +// document edits. +using AnalyzerReferenceMap = ConditionalWeakTable, AsyncLazy>; + +internal partial class SolutionCompilationState +{ + private sealed record SourceGeneratorMap( + ImmutableArray SourceGenerators, + FrozenDictionary SourceGeneratorToAnalyzerReference); + + /// + /// Cached mapping from language (only C#/VB since those are the only languages that support analyzers) to the lists + /// of analyzer references (see ) to all the s produced by those references. This should only be created and cached on the OOP side + /// of things so that we don't cause source generators to be loaded (and fixed) within VS (which is .net framework + /// only). + /// + private static readonly ConditionalWeakTable s_projectStateToSourceGeneratorsMap = new(); + + /// + /// Cached information about if a project has source generators or not. Note: this is distinct from as we want to be able to compute it by calling over to our OOP + /// process (if present) and having it make the determination, without the host necessarily loading generators + /// itself. + /// + private static readonly Dictionary s_languageToAnalyzerReferenceMap = new() + { + { LanguageNames.CSharp, new() }, + { LanguageNames.VisualBasic, new() }, + }; + + /// + /// This method should only be called in a .net core host like our out of process server. + /// + private static ImmutableArray GetSourceGenerators(ProjectState projectState) + { + var map = GetSourceGeneratorMap(projectState); + return map is null ? [] : map.SourceGenerators; + } + + /// + /// This method should only be called in a .net core host like our out of process server. + /// + private static AnalyzerReference GetAnalyzerReference(ProjectState projectState, ISourceGenerator sourceGenerator) + { + // We must be talking about a project that supports compilations, since we already got a source generator from it. + Contract.ThrowIfFalse(projectState.SupportsCompilation); + + var map = GetSourceGeneratorMap(projectState); + + // It should not be possible for this to be null. We have the source generator, as such we must have mapped from + // the project state to the SG info for it. + Contract.ThrowIfNull(map); + + return map.SourceGeneratorToAnalyzerReference[sourceGenerator]; + } + + private static SourceGeneratorMap? GetSourceGeneratorMap(ProjectState projectState) + { + if (!projectState.SupportsCompilation) + return null; + + return s_projectStateToSourceGeneratorsMap.GetValue(projectState, ComputeSourceGenerators); + + static SourceGeneratorMap ComputeSourceGenerators(ProjectState projectState) + { + using var generators = TemporaryArray.Empty; + using var _ = PooledDictionary.GetInstance(out var generatorToAnalyzerReference); + + foreach (var reference in projectState.AnalyzerReferences) + { + foreach (var generator in reference.GetGenerators(projectState.Language).Distinct()) + { + generators.Add(generator); + generatorToAnalyzerReference.Add(generator, reference); + } + } + + return new(generators.ToImmutableAndClear(), generatorToAnalyzerReference.ToFrozenDictionary()); + } + } + + public async Task HasSourceGeneratorsAsync(ProjectId projectId, CancellationToken cancellationToken) + { + var projectState = this.SolutionState.GetRequiredProjectState(projectId); + if (projectState.AnalyzerReferences.Count == 0) + return false; + + if (!RemoteSupportedLanguages.IsSupported(projectState.Language)) + return false; + + var analyzerReferenceMap = s_languageToAnalyzerReferenceMap[projectState.Language]; + if (!analyzerReferenceMap.TryGetValue(projectState.AnalyzerReferences, out var lazy)) + { + // Extracted into local function to prevent allocations in the case where we find a value in the cache. + lazy = GetLazy(analyzerReferenceMap, projectState); + } + + return await lazy.GetValueAsync(cancellationToken).ConfigureAwait(false); + + AsyncLazy GetLazy(AnalyzerReferenceMap analyzerReferenceMap, ProjectState projectState) + => analyzerReferenceMap.GetValue( + projectState.AnalyzerReferences, + _ => AsyncLazy.Create( + cancellationToken => ComputeHasSourceGeneratorsAsync(this, projectState, cancellationToken))); + + static async Task ComputeHasSourceGeneratorsAsync( + SolutionCompilationState solution, ProjectState projectState, CancellationToken cancellationToken) + { + var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); + // If in proc, just load the generators and see if we have any. + if (client is null) + return GetSourceGenerators(projectState).Any(); + + // Out of process, call to the remote to figure this out. + var projectId = projectState.Id; + var projectStateChecksums = await projectState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + var analyzerReferences = projectStateChecksums.AnalyzerReferences.Children; + + var result = await client.TryInvokeAsync( + solution, + projectId, + (service, solution, cancellationToken) => service.HasGeneratorsAsync(solution, projectId, analyzerReferences, projectState.Language, cancellationToken), + cancellationToken).ConfigureAwait(false); + return result.HasValue && result.Value; + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 3e6200977dde5..9600215792ec1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -33,6 +33,12 @@ internal readonly record struct StateChange( /// internal sealed partial class SolutionState { + /// + /// Note: this insensitive comparer is busted on many systems. But we do things this way for compat with the logic + /// we've had on windows since forever. + /// + public static readonly StringComparer FilePathComparer = StringComparer.OrdinalIgnoreCase; + // the version of the workspace this solution is from public int WorkspaceVersion { get; } public string? WorkspaceKind { get; } @@ -42,12 +48,13 @@ internal sealed partial class SolutionState private readonly SolutionInfo.SolutionAttributes _solutionAttributes; private readonly ImmutableDictionary _projectIdToProjectStateMap; - private readonly FilePathToDocumentIdsMap _filePathToDocumentIdsMap; private readonly ProjectDependencyGraph _dependencyGraph; // holds on data calculated based on the AnalyzerReferences list private readonly Lazy _lazyAnalyzers; + private ImmutableDictionary> _lazyFilePathToRelatedDocumentIds = ImmutableDictionary>.Empty.WithComparers(FilePathComparer); + private SolutionState( string? workspaceKind, int workspaceVersion, @@ -57,7 +64,6 @@ private SolutionState( SolutionOptionSet options, IReadOnlyList analyzerReferences, ImmutableDictionary idToProjectStateMap, - FilePathToDocumentIdsMap filePathToDocumentIdsMap, ProjectDependencyGraph dependencyGraph, Lazy? lazyAnalyzers) { @@ -69,7 +75,6 @@ private SolutionState( Options = options; AnalyzerReferences = analyzerReferences; _projectIdToProjectStateMap = idToProjectStateMap; - _filePathToDocumentIdsMap = filePathToDocumentIdsMap; _dependencyGraph = dependencyGraph; _lazyAnalyzers = lazyAnalyzers ?? CreateLazyHostDiagnosticAnalyzers(analyzerReferences); @@ -100,7 +105,6 @@ public SolutionState( options, analyzerReferences, idToProjectStateMap: ImmutableDictionary.Empty, - filePathToDocumentIdsMap: FilePathToDocumentIdsMap.Empty, dependencyGraph: ProjectDependencyGraph.Empty, lazyAnalyzers: null) { @@ -152,7 +156,6 @@ internal SolutionState Branch( SolutionOptionSet? options = null, IReadOnlyList? analyzerReferences = null, ImmutableDictionary? idToProjectStateMap = null, - FilePathToDocumentIdsMap? filePathToDocumentIdsMap = null, ProjectDependencyGraph? dependencyGraph = null) { solutionAttributes ??= _solutionAttributes; @@ -160,7 +163,6 @@ internal SolutionState Branch( idToProjectStateMap ??= _projectIdToProjectStateMap; options ??= Options; analyzerReferences ??= AnalyzerReferences; - filePathToDocumentIdsMap ??= _filePathToDocumentIdsMap; dependencyGraph ??= _dependencyGraph; var analyzerReferencesEqual = AnalyzerReferences.SequenceEqual(analyzerReferences); @@ -170,7 +172,6 @@ internal SolutionState Branch( options == Options && analyzerReferencesEqual && idToProjectStateMap == _projectIdToProjectStateMap && - filePathToDocumentIdsMap == _filePathToDocumentIdsMap && dependencyGraph == _dependencyGraph) { return this; @@ -185,7 +186,6 @@ internal SolutionState Branch( options, analyzerReferences, idToProjectStateMap, - filePathToDocumentIdsMap.Value, dependencyGraph, analyzerReferencesEqual ? _lazyAnalyzers : null); } @@ -218,13 +218,10 @@ public SolutionState WithNewWorkspace( Options, AnalyzerReferences, _projectIdToProjectStateMap, - _filePathToDocumentIdsMap, _dependencyGraph, _lazyAnalyzers); } - public FilePathToDocumentIdsMap FilePathToDocumentIdsMap => _filePathToDocumentIdsMap; - /// /// The version of the most recently modified project. /// @@ -298,158 +295,135 @@ public ProjectState GetRequiredProjectState(ProjectId projectId) return result; } - private SolutionState AddProject(ProjectState projectState) + /// + /// Create a new solution instance that includes projects with the specified project information. + /// + public SolutionState AddProjects(ArrayBuilder projectInfos) { - var projectId = projectState.Id; + Contract.ThrowIfTrue(projectInfos.HasDuplicates(static p => p.Id), "Duplicate ProjectId provided"); - // changed project list so, increment version. - var newSolutionAttributes = _solutionAttributes.With(version: Version.GetNewerVersion()); + if (projectInfos.Count == 0) + return this; - var newProjectIds = ProjectIds.ToImmutableArray().Add(projectId); - var newStateMap = _projectIdToProjectStateMap.Add(projectId, projectState); + using var _ = ArrayBuilder.GetInstance(projectInfos.Count, out var projectStates); + foreach (var projectInfo in projectInfos) + projectStates.Add(CreateProjectState(projectInfo)); - var newDependencyGraph = _dependencyGraph - .WithAdditionalProject(projectId) - .WithAdditionalProjectReferences(projectId, projectState.ProjectReferences); + return AddProjects(projectStates); - // It's possible that another project already in newStateMap has a reference to this project that we're adding, since we allow - // dangling references like that. If so, we'll need to link those in too. - foreach (var newState in newStateMap) + ProjectState CreateProjectState(ProjectInfo projectInfo) { - foreach (var projectReference in newState.Value.ProjectReferences) - { - if (projectReference.ProjectId == projectId) - { - newDependencyGraph = newDependencyGraph.WithAdditionalProjectReferences( - newState.Key, - SpecializedCollections.SingletonReadOnlyList(projectReference)); + if (projectInfo == null) + throw new ArgumentNullException(nameof(projectInfo)); - break; - } - } - } + var projectId = projectInfo.Id; - var newFilePathToDocumentIdsMap = CreateFilePathToDocumentIdsMapWithAddedDocuments(GetDocumentStates(newStateMap[projectId])); + var language = projectInfo.Language; + if (language == null) + throw new ArgumentNullException(nameof(language)); - return Branch( - solutionAttributes: newSolutionAttributes, - projectIds: newProjectIds, - idToProjectStateMap: newStateMap, - filePathToDocumentIdsMap: newFilePathToDocumentIdsMap, - dependencyGraph: newDependencyGraph); - } + var displayName = projectInfo.Name; + if (displayName == null) + throw new ArgumentNullException(nameof(displayName)); - /// - /// Create a new solution instance that includes a project with the specified project information. - /// - public SolutionState AddProject(ProjectInfo projectInfo) - { - if (projectInfo == null) - { - throw new ArgumentNullException(nameof(projectInfo)); - } + CheckNotContainsProject(projectId); - var projectId = projectInfo.Id; + var languageServices = Services.GetLanguageServices(language); + if (languageServices == null) + throw new ArgumentException(string.Format(WorkspacesResources.The_language_0_is_not_supported, language)); - var language = projectInfo.Language; - if (language == null) - { - throw new ArgumentNullException(nameof(language)); + var newProject = new ProjectState(languageServices, projectInfo); + return newProject; } - var displayName = projectInfo.Name; - if (displayName == null) + SolutionState AddProjects(ArrayBuilder projectStates) { - throw new ArgumentNullException(nameof(displayName)); - } + // changed project list so, increment version. + var newSolutionAttributes = _solutionAttributes.With(version: Version.GetNewerVersion()); - CheckNotContainsProject(projectId); + using var _1 = ArrayBuilder.GetInstance(ProjectIds.Count + projectStates.Count, out var newProjectIdsBuilder); + using var _2 = PooledHashSet.GetInstance(out var addedProjectIds); + var newStateMapBuilder = _projectIdToProjectStateMap.ToBuilder(); - var languageServices = Services.GetLanguageServices(language); - if (languageServices == null) - { - throw new ArgumentException(string.Format(WorkspacesResources.The_language_0_is_not_supported, language)); - } + newProjectIdsBuilder.AddRange(ProjectIds); - var newProject = new ProjectState(languageServices, projectInfo); + foreach (var projectState in projectStates) + { + addedProjectIds.Add(projectState.Id); + newProjectIdsBuilder.Add(projectState.Id); + newStateMapBuilder.Add(projectState.Id, projectState); + } - return this.AddProject(newProject); - } + var newProjectIds = newProjectIdsBuilder.ToBoxedImmutableArray(); + var newStateMap = newStateMapBuilder.ToImmutable(); + + // TODO: it would be nice to update these graphs without so much forking. + var newDependencyGraph = _dependencyGraph; + foreach (var projectState in projectStates) + { + var projectId = projectState.Id; + newDependencyGraph = newDependencyGraph + .WithAdditionalProject(projectId) + .WithAdditionalProjectReferences(projectId, projectState.ProjectReferences); + } + + // It's possible that another project already in newStateMap has a reference to this project that we're adding, + // since we allow dangling references like that. If so, we'll need to link those in too. + foreach (var (projectId, newState) in newStateMap) + { + foreach (var projectReference in newState.ProjectReferences) + { + if (addedProjectIds.Contains(projectReference.ProjectId)) + newDependencyGraph = newDependencyGraph.WithAdditionalProjectReferences(projectId, [projectReference]); + } + } - private static IEnumerable GetDocumentStates(ProjectState projectState) - => projectState.DocumentStates.States.Values - .Concat(projectState.AdditionalDocumentStates.States.Values) - .Concat(projectState.AnalyzerConfigDocumentStates.States.Values); + return Branch( + solutionAttributes: newSolutionAttributes, + projectIds: newProjectIds, + idToProjectStateMap: newStateMap, + dependencyGraph: newDependencyGraph); + } + } /// - /// Create a new solution instance without the project specified. + /// Create a new solution instance without the projects specified. /// - public SolutionState RemoveProject(ProjectId projectId) + public SolutionState RemoveProjects(ArrayBuilder projectIds) { - if (projectId == null) - { - throw new ArgumentNullException(nameof(projectId)); - } + Contract.ThrowIfTrue(projectIds.HasDuplicates(), "Duplicate ProjectId provided"); - CheckContainsProject(projectId); + if (projectIds.Count == 0) + return this; + + foreach (var projectId in projectIds) + CheckContainsProject(projectId); // changed project list so, increment version. var newSolutionAttributes = _solutionAttributes.With(version: this.Version.GetNewerVersion()); - var newProjectIds = ProjectIds.ToImmutableArray().Remove(projectId); - var newStateMap = _projectIdToProjectStateMap.Remove(projectId); - var newDependencyGraph = _dependencyGraph.WithProjectRemoved(projectId); - var newFilePathToDocumentIdsMap = CreateFilePathToDocumentIdsMapWithRemovedDocuments(GetDocumentStates(_projectIdToProjectStateMap[projectId])); + using var _ = PooledHashSet.GetInstance(out var projectIdsSet); + projectIdsSet.AddRange(projectIds); + + var newProjectIds = ProjectIds.Where(p => !projectIdsSet.Contains(p)).ToBoxedImmutableArray(); + + var newStateMapBuilder = _projectIdToProjectStateMap.ToBuilder(); + foreach (var projectId in projectIds) + newStateMapBuilder.Remove(projectId); + var newStateMap = newStateMapBuilder.ToImmutable(); + + // Note: it would be nice to not cause N forks of the dependency graph here. + var newDependencyGraph = _dependencyGraph; + foreach (var projectId in projectIds) + newDependencyGraph = newDependencyGraph.WithProjectRemoved(projectId); return this.Branch( solutionAttributes: newSolutionAttributes, projectIds: newProjectIds, idToProjectStateMap: newStateMap, - filePathToDocumentIdsMap: newFilePathToDocumentIdsMap, dependencyGraph: newDependencyGraph); } - public FilePathToDocumentIdsMap CreateFilePathToDocumentIdsMapWithAddedDocuments(IEnumerable documentStates) - { - var builder = _filePathToDocumentIdsMap.ToBuilder(); - AddDocumentFilePaths(documentStates, builder); - return builder.ToImmutable(); - } - - private static void AddDocumentFilePaths(IEnumerable documentStates, FilePathToDocumentIdsMap.Builder builder) - { - foreach (var documentState in documentStates) - builder.Add(documentState.FilePath, documentState.Id); - } - - public FilePathToDocumentIdsMap CreateFilePathToDocumentIdsMapWithRemovedDocuments(IEnumerable documentStates) - { - var builder = _filePathToDocumentIdsMap.ToBuilder(); - RemoveDocumentFilePaths(documentStates, builder); - return builder.ToImmutable(); - } - - private static void RemoveDocumentFilePaths(IEnumerable documentStates, FilePathToDocumentIdsMap.Builder builder) - { - foreach (var documentState in documentStates) - builder.Remove(documentState.FilePath, documentState.Id); - } - - private FilePathToDocumentIdsMap CreateFilePathToDocumentIdsMapWithFilePath(DocumentId documentId, string? oldFilePath, string? newFilePath) - { - if (oldFilePath == newFilePath) - { - return _filePathToDocumentIdsMap; - } - - var builder = _filePathToDocumentIdsMap.ToBuilder(); - - builder.Remove(oldFilePath, documentId); - builder.Add(newFilePath, documentId); - - return builder.ToImmutable(); - } - /// /// Creates a new solution instance with the project specified updated to have the new /// assembly name. @@ -1118,13 +1092,9 @@ private StateChange UpdateDocumentState(DocumentState newDocument, bool contentC // This method shouldn't have been called if the document has not changed. Debug.Assert(oldProject != newProject); - var oldDocument = oldProject.DocumentStates.GetRequiredState(newDocument.Id); - var newFilePathToDocumentIdsMap = CreateFilePathToDocumentIdsMapWithFilePath(newDocument.Id, oldDocument.FilePath, newDocument.FilePath); - return ForkProject( oldProject, - newProject, - newFilePathToDocumentIdsMap: newFilePathToDocumentIdsMap); + newProject); } private StateChange UpdateAdditionalDocumentState(AdditionalDocumentState newDocument, bool contentChanged) @@ -1158,8 +1128,7 @@ private StateChange UpdateAnalyzerConfigDocumentState(AnalyzerConfigDocumentStat public StateChange ForkProject( ProjectState oldProjectState, ProjectState newProjectState, - ProjectDependencyGraph? newDependencyGraph = null, - FilePathToDocumentIdsMap? newFilePathToDocumentIdsMap = null) + ProjectDependencyGraph? newDependencyGraph = null) { var projectId = newProjectState.Id; @@ -1170,8 +1139,7 @@ public StateChange ForkProject( var newSolutionState = this.Branch( idToProjectStateMap: newStateMap, - dependencyGraph: newDependencyGraph, - filePathToDocumentIdsMap: newFilePathToDocumentIdsMap ?? _filePathToDocumentIdsMap); + dependencyGraph: newDependencyGraph); return new(newSolutionState, oldProjectState, newProjectState); } @@ -1185,38 +1153,19 @@ public ImmutableArray GetDocumentIdsWithFilePath(string? filePath) if (string.IsNullOrEmpty(filePath)) return []; - if (!_filePathToDocumentIdsMap.TryGetValue(filePath, out var documentIds)) - return []; + return ImmutableInterlocked.GetOrAdd( + ref _lazyFilePathToRelatedDocumentIds, + filePath, + static (filePath, @this) => ComputeDocumentIdsWithFilePath(@this, filePath), + this); - // If this wasn't the result of a freeze, then we can return the document ids as is. They should be accurate. - if (!_filePathToDocumentIdsMap.IsFrozen) + static ImmutableArray ComputeDocumentIdsWithFilePath(SolutionState @this, string filePath) { - Debug.Assert(documentIds.All(ContainsAnyDocument)); - return documentIds; - } + using var result = TemporaryArray.Empty; + foreach (var (projectId, projectState) in @this.ProjectStates) + projectState.AddDocumentIdsWithFilePath(ref result.AsRef(), filePath); - // We were frozen. So we may be seeing document ids that no longer exist within this snapshot. If so, - // filter them out. - using var _ = ArrayBuilder.GetInstance(documentIds.Length, out var result); - - foreach (var documentId in documentIds) - { - if (ContainsAnyDocument(documentId)) - result.Add(documentId); - } - - result.RemoveDuplicates(); - return result.ToImmutableAndClear(); - - bool ContainsAnyDocument(DocumentId documentId) - { - var project = this.GetProjectState(documentId.ProjectId); - if (project is null) - return false; - - return project.DocumentStates.Contains(documentId) - || project.AdditionalDocumentStates.Contains(documentId) - || project.AnalyzerConfigDocumentStates.Contains(documentId); + return result.ToImmutableAndClear(); } } @@ -1224,12 +1173,12 @@ public static ProjectDependencyGraph CreateDependencyGraph( IReadOnlyList projectIds, ImmutableDictionary projectStates) { - var map = projectStates.Values.Select(state => new KeyValuePair>( + var map = projectStates.Values.Select(state => KeyValuePairUtil.Create( state.Id, state.ProjectReferences.Where(pr => projectStates.ContainsKey(pr.ProjectId)).Select(pr => pr.ProjectId).ToImmutableHashSet())) .ToImmutableDictionary(); - return new ProjectDependencyGraph(projectIds.ToImmutableHashSet(), map); + return new ProjectDependencyGraph([.. projectIds], map); } public SolutionState WithOptions(SolutionOptionSet options) @@ -1269,6 +1218,59 @@ public SolutionState WithAnalyzerReferences(IReadOnlyList ana return Branch(analyzerReferences: analyzerReferences); } + public DocumentId? GetFirstRelatedDocumentId(DocumentId documentId, ProjectId? relatedProjectIdHint) + { + Contract.ThrowIfTrue(documentId.ProjectId == relatedProjectIdHint); + + var projectState = this.GetProjectState(documentId.ProjectId); + if (projectState is null) + return null; + + var documentState = projectState.DocumentStates.GetState(documentId); + if (documentState is null) + return null; + + var filePath = documentState.FilePath; + if (string.IsNullOrEmpty(filePath)) + return null; + + // Do a quick check if the full info for that path has already been computed and cached. + var fileMap = _lazyFilePathToRelatedDocumentIds; + if (fileMap != null && fileMap.TryGetValue(filePath, out var relatedDocumentIds)) + { + foreach (var relatedDocumentId in relatedDocumentIds) + { + if (relatedDocumentId != documentId) + return relatedDocumentId; + } + + return null; + } + + var relatedProject = relatedProjectIdHint is null ? null : this.ProjectStates[relatedProjectIdHint]; + Contract.ThrowIfTrue(relatedProject == projectState); + if (relatedProject != null) + { + var siblingDocumentId = relatedProject.GetFirstDocumentIdWithFilePath(filePath); + if (siblingDocumentId is not null) + return siblingDocumentId; + } + + // Wasn't in cache, do the linear search. + foreach (var (_, siblingProjectState) in this.ProjectStates) + { + // Don't want to search the same project that document already came from, or from the related-project we had a hint for. + if (siblingProjectState == projectState || siblingProjectState == relatedProject) + continue; + + var siblingDocumentId = siblingProjectState.GetFirstDocumentIdWithFilePath(filePath); + if (siblingDocumentId is not null) + return siblingDocumentId; + } + + return null; + } + public ImmutableArray GetRelatedDocumentIds(DocumentId documentId) { var projectState = this.GetProjectState(documentId.ProjectId); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs new file mode 100644 index 0000000000000..32bebdc86d8d5 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis; + +public partial class Solution +{ + /// + /// Strongly held reference to the semantic model for the active document. By strongly holding onto it, we ensure + /// that it won't be GC'ed between feature requests from multiple features that care about it. As the active + /// document has the most features running on it continuously, we definitely do not want to drop this. Note: this + /// cached value is only to help with performance. Not with correctness. Importantly, the concept of 'active + /// document' is itself fundamentally racy. That's ok though as we simply want to settle on these semantic models + /// settling into a stable state over time. We don't need to be perfect about it. They are intentionally not + /// locked either as we would only have contention right when switching to a new active document, and we would still + /// latch onto the new document very quickly. + /// + /// + /// It is fine for these fields to never be read. The purpose is simply to keep a strong reference around so that + /// they will not be GC'ed as long as the active document stays the same. + /// +#pragma warning disable IDE0052 // Remove unread private members + private SemanticModel? _activeDocumentSemanticModel; + + /// + private SemanticModel? _activeDocumentNullableDisabledSemanticModel; +#pragma warning restore IDE0052 // Remove unread private members + + internal void OnSemanticModelObtained(DocumentId documentId, SemanticModel semanticModel) + { + var service = this.Services.GetRequiredService(); + + var activeDocumentId = service.TryGetActiveDocument(); + if (activeDocumentId is null) + { + // no active document? then clear out any caches we have. + _activeDocumentSemanticModel = null; + _activeDocumentNullableDisabledSemanticModel = null; + } + else if (activeDocumentId != documentId) + { + // We have an active document, but we just obtained the semantic model for some other doc. Nothing to do + // here, we don't want to cache this. + return; + } + else + { + // Ok. We just obtained the semantic model for the active document. Make a strong reference to it so that + // other features that wake up for this active document are sure to be able to reuse the same one. +#pragma warning disable RSEXPERIMENTAL001 // sym-shipped usage of experimental API + if (semanticModel.NullableAnalysisIsDisabled) + _activeDocumentNullableDisabledSemanticModel = semanticModel; + else + _activeDocumentSemanticModel = semanticModel; +#pragma warning restore RSEXPERIMENTAL001 + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocument.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocument.cs index f772be5a2ba1c..2a2a9d97a6668 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocument.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocument.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.Threading; namespace Microsoft.CodeAnalysis; @@ -23,6 +24,8 @@ internal SourceGeneratedDocument(Project project, SourceGeneratedDocumentState s // TODO: make this public. Tracked by https://github.com/dotnet/roslyn/issues/50546 internal SourceGeneratedDocumentIdentity Identity => State.Identity; + internal DateTime GenerationDateTime => State.GenerationDateTime; + internal override Document WithFrozenPartialSemantics(CancellationToken cancellationToken) { // For us to implement frozen partial semantics here with a source generated document, diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs index 81bfcb7a765e8..cb1591b0eb256 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs @@ -38,12 +38,15 @@ internal sealed class SourceGeneratedDocumentState : DocumentState public Checksum GetOriginalSourceTextContentHash() => _lazyContentHash.Value; + public readonly DateTime GenerationDateTime; + public static SourceGeneratedDocumentState Create( SourceGeneratedDocumentIdentity documentIdentity, SourceText generatedSourceText, ParseOptions parseOptions, LanguageServices languageServices, - Checksum? originalSourceTextChecksum) + Checksum? originalSourceTextChecksum, + DateTime generationDateTime) { // If the caller explicitly provided us with the checksum for the source text, then we always defer to that. // This happens on the host side, when we are given the data computed by the OOP side. @@ -51,7 +54,7 @@ public static SourceGeneratedDocumentState Create( // If the caller didn't provide us with the checksum, then we'll compute it on demand. This happens on the OOP // side when we're actually producing the SG doc in the first place. var lazyTextChecksum = new Lazy(() => originalSourceTextChecksum ?? ComputeContentHash(generatedSourceText)); - return Create(documentIdentity, generatedSourceText, parseOptions, languageServices, lazyTextChecksum); + return Create(documentIdentity, generatedSourceText, parseOptions, languageServices, lazyTextChecksum, generationDateTime); } private static SourceGeneratedDocumentState Create( @@ -59,7 +62,8 @@ private static SourceGeneratedDocumentState Create( SourceText generatedSourceText, ParseOptions parseOptions, LanguageServices languageServices, - Lazy lazyTextChecksum) + Lazy lazyTextChecksum, + DateTime generationDateTime) { var loadTextOptions = new LoadTextOptions(generatedSourceText.ChecksumAlgorithm); var textAndVersion = TextAndVersion.Create(generatedSourceText, VersionStamp.Create()); @@ -78,7 +82,7 @@ private static SourceGeneratedDocumentState Create( new DocumentInfo.DocumentAttributes( documentIdentity.DocumentId, name: documentIdentity.HintName, - folders: SpecializedCollections.EmptyReadOnlyList(), + folders: [], parseOptions.Kind, filePath: documentIdentity.FilePath, isGenerated: true, @@ -88,7 +92,8 @@ private static SourceGeneratedDocumentState Create( generatedSourceText, loadTextOptions, treeSource, - lazyTextChecksum); + lazyTextChecksum, + generationDateTime); } private SourceGeneratedDocumentState( @@ -97,17 +102,19 @@ private SourceGeneratedDocumentState( IDocumentServiceProvider? documentServiceProvider, DocumentInfo.DocumentAttributes attributes, ParseOptions options, - ConstantTextAndVersionSource textSource, + ITextAndVersionSource textSource, SourceText text, LoadTextOptions loadTextOptions, - AsyncLazy treeSource, - Lazy lazyContentHash) + ITreeAndVersionSource treeSource, + Lazy lazyContentHash, + DateTime generationDateTime) : base(languageServices, documentServiceProvider, attributes, options, textSource, loadTextOptions, treeSource) { Identity = documentIdentity; SourceText = text; _lazyContentHash = lazyContentHash; + GenerationDateTime = generationDateTime; } private static Checksum ComputeContentHash(SourceText text) @@ -135,7 +142,8 @@ public SourceGeneratedDocumentState WithText(SourceText sourceText) ParseOptions, LanguageServices, // Just pass along the checksum for the new source text since we've already computed it. - newSourceTextChecksum); + newSourceTextChecksum, + GenerationDateTime); } public SourceGeneratedDocumentState WithParseOptions(ParseOptions parseOptions) @@ -150,7 +158,30 @@ public SourceGeneratedDocumentState WithParseOptions(ParseOptions parseOptions) parseOptions, LanguageServices, // We're just changing the parse options. So the checksum will remain as is. - _lazyContentHash); + _lazyContentHash, + GenerationDateTime); + } + + public SourceGeneratedDocumentState WithGenerationDateTime(DateTime generationDateTime) + { + // See if we can reuse this instance directly + if (this.GenerationDateTime == generationDateTime) + return this; + + // Copy over all state as-is. The generation time doesn't change any actual state (the same tree will be + // produced for example). + return new( + this.Identity, + this.LanguageServices, + this.Services, + this.Attributes, + this.ParseOptions, + this.TextAndVersionSource, + this.SourceText, + this.LoadTextOptions, + this.TreeSource!, + this._lazyContentHash, + generationDateTime); } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs new file mode 100644 index 0000000000000..765e3cae91379 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratorExecutionVersion.cs @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more 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.Runtime.Serialization; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Host; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis; + +/// +/// Represents the version of source generator execution that a project is at. Source generator results are kept around +/// as long as this version stays the same and we are in +/// mode. This has no effect when in mode (as we always rerun +/// generators on any change). This should effectively be used as a monotonically increasing value. +/// +/// Controls the major version of source generation execution. When this changes the +/// generator driver should be dropped and all generation should be rerun. +/// Controls the minor version of source generation execution. When this changes the +/// generator driver can be reused and should incrementally determine what the new generated documents should be. +/// +[DataContract] +internal readonly record struct SourceGeneratorExecutionVersion( + [property: DataMember(Order = 0)] int MajorVersion, + [property: DataMember(Order = 1)] int MinorVersion) +{ + public SourceGeneratorExecutionVersion IncrementMajorVersion() + => new(MajorVersion + 1, MinorVersion: 0); + + public SourceGeneratorExecutionVersion IncrementMinorVersion() + => new(MajorVersion, MinorVersion + 1); + + public void WriteTo(ObjectWriter writer) + { + writer.WriteInt32(MajorVersion); + writer.WriteInt32(MinorVersion); + } + + public static SourceGeneratorExecutionVersion ReadFrom(ObjectReader reader) + => new(reader.ReadInt32(), reader.ReadInt32()); +} + +/// +/// Helper construct to allow a mapping from s to . +/// Limited to just the surface area the workspace needs. +/// +internal sealed class SourceGeneratorExecutionVersionMap(ImmutableSegmentedDictionary map) +{ + public static readonly SourceGeneratorExecutionVersionMap Empty = new(); + + public ImmutableSegmentedDictionary Map { get; } = map; + + public SourceGeneratorExecutionVersionMap() + : this(ImmutableSegmentedDictionary.Empty) + { + } + + public SourceGeneratorExecutionVersion this[ProjectId projectId] => Map[projectId]; + + public static bool operator ==(SourceGeneratorExecutionVersionMap map1, SourceGeneratorExecutionVersionMap map2) + => map1.Map == map2.Map; + + public static bool operator !=(SourceGeneratorExecutionVersionMap map1, SourceGeneratorExecutionVersionMap map2) + => !(map1 == map2); + + public override int GetHashCode() + => throw new InvalidOperationException(); + + public override bool Equals([NotNullWhen(true)] object? obj) + => obj is SourceGeneratorExecutionVersionMap map && this == map; + + public void WriteTo(ObjectWriter writer) + { + writer.WriteInt32(Map.Count); + foreach (var (projectId, version) in Map) + { + projectId.WriteTo(writer); + version.WriteTo(writer); + } + } + + public static SourceGeneratorExecutionVersionMap Deserialize(ObjectReader reader) + { + var count = reader.ReadInt32(); + var builder = ImmutableSegmentedDictionary.CreateBuilder(); + for (var i = 0; i < count; i++) + { + var projectId = ProjectId.ReadFrom(reader); + var version = SourceGeneratorExecutionVersion.ReadFrom(reader); + builder.Add(projectId, version); + } + + return new(builder.ToImmutable()); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index e030b033f7275..080785d0a61fe 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -5,11 +5,13 @@ using System; using System.Collections.Frozen; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Serialization; @@ -18,8 +20,11 @@ internal sealed class SolutionCompilationStateChecksums { public SolutionCompilationStateChecksums( Checksum solutionState, + Checksum sourceGeneratorExecutionVersionMap, + // These arrays are all the same length if present, and reference the same documents in the same order. + DocumentChecksumsAndIds? frozenSourceGeneratedDocuments, ChecksumCollection? frozenSourceGeneratedDocumentIdentities, - ChecksumsAndIds? frozenSourceGeneratedDocuments) + ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes) { // For the frozen source generated document info, we expect two either have both checksum collections or neither, and they // should both be the same length as there is a 1:1 correspondence between them. @@ -27,26 +32,40 @@ public SolutionCompilationStateChecksums( Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities?.Count == frozenSourceGeneratedDocuments?.Length); SolutionState = solutionState; - FrozenSourceGeneratedDocumentIdentities = frozenSourceGeneratedDocumentIdentities; + SourceGeneratorExecutionVersionMap = sourceGeneratorExecutionVersionMap; FrozenSourceGeneratedDocuments = frozenSourceGeneratedDocuments; + FrozenSourceGeneratedDocumentIdentities = frozenSourceGeneratedDocumentIdentities; + FrozenSourceGeneratedDocumentGenerationDateTimes = frozenSourceGeneratedDocumentGenerationDateTimes; + // note: intentionally not mixing in FrozenSourceGeneratedDocumentGenerationDateTimes as that is not part of the + // identity contract of this type. Checksum = Checksum.Create( SolutionState, + SourceGeneratorExecutionVersionMap, FrozenSourceGeneratedDocumentIdentities?.Checksum ?? Checksum.Null, - FrozenSourceGeneratedDocuments?.Checksum ?? Checksum.Null); + frozenSourceGeneratedDocuments?.Checksum ?? Checksum.Null); } public Checksum Checksum { get; } public Checksum SolutionState { get; } + public Checksum SourceGeneratorExecutionVersionMap { get; } + + /// + /// Checksums of the SourceTexts of the frozen documents directly. Not checksums of their DocumentStates. + /// + public DocumentChecksumsAndIds? FrozenSourceGeneratedDocuments { get; } public ChecksumCollection? FrozenSourceGeneratedDocumentIdentities { get; } - public ChecksumsAndIds? FrozenSourceGeneratedDocuments { get; } + + // note: intentionally not part of the identity contract of this type. + public ImmutableArray FrozenSourceGeneratedDocumentGenerationDateTimes { get; } public void AddAllTo(HashSet checksums) { checksums.AddIfNotNullChecksum(this.Checksum); checksums.AddIfNotNullChecksum(this.SolutionState); + checksums.AddIfNotNullChecksum(this.SourceGeneratorExecutionVersionMap); this.FrozenSourceGeneratedDocumentIdentities?.AddAllTo(checksums); - this.FrozenSourceGeneratedDocuments?.Checksums.AddAllTo(checksums); + this.FrozenSourceGeneratedDocuments?.AddAllTo(checksums); } public void Serialize(ObjectWriter writer) @@ -54,13 +73,15 @@ public void Serialize(ObjectWriter writer) // Writing this is optional, but helps ensure checksums are being computed properly on both the host and oop side. this.Checksum.WriteTo(writer); this.SolutionState.WriteTo(writer); + this.SourceGeneratorExecutionVersionMap.WriteTo(writer); // Write out a boolean to know whether we'll have this extra information writer.WriteBoolean(this.FrozenSourceGeneratedDocumentIdentities.HasValue); if (FrozenSourceGeneratedDocumentIdentities.HasValue) { - this.FrozenSourceGeneratedDocumentIdentities.Value.WriteTo(writer); this.FrozenSourceGeneratedDocuments!.Value.WriteTo(writer); + this.FrozenSourceGeneratedDocumentIdentities.Value.WriteTo(writer); + writer.WriteArray(this.FrozenSourceGeneratedDocumentGenerationDateTimes, static (w, d) => w.WriteInt64(d.Ticks)); } } @@ -68,58 +89,97 @@ public static SolutionCompilationStateChecksums Deserialize(ObjectReader reader) { var checksum = Checksum.ReadFrom(reader); var solutionState = Checksum.ReadFrom(reader); + var sourceGeneratorExecutionVersionMap = Checksum.ReadFrom(reader); var hasFrozenSourceGeneratedDocuments = reader.ReadBoolean(); + DocumentChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; - ChecksumsAndIds? frozenSourceGeneratedDocuments = null; + ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes = default; if (hasFrozenSourceGeneratedDocuments) { + frozenSourceGeneratedDocumentTexts = DocumentChecksumsAndIds.ReadFrom(reader); frozenSourceGeneratedDocumentIdentities = ChecksumCollection.ReadFrom(reader); - frozenSourceGeneratedDocuments = ChecksumsAndIds.ReadFrom(reader); + frozenSourceGeneratedDocumentGenerationDateTimes = reader.ReadArray(r => new DateTime(r.ReadInt64())); } var result = new SolutionCompilationStateChecksums( solutionState: solutionState, - frozenSourceGeneratedDocumentIdentities: frozenSourceGeneratedDocumentIdentities, - frozenSourceGeneratedDocuments: frozenSourceGeneratedDocuments); + sourceGeneratorExecutionVersionMap: sourceGeneratorExecutionVersionMap, + frozenSourceGeneratedDocumentTexts, + frozenSourceGeneratedDocumentIdentities, + frozenSourceGeneratedDocumentGenerationDateTimes); Contract.ThrowIfFalse(result.Checksum == checksum); return result; } - public async Task FindAsync( + public async Task FindAsync( SolutionCompilationState compilationState, ProjectCone? projectCone, - AssetHint assetHint, + AssetPath assetPath, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (searchingChecksumsLeft.Count == 0) return; - // verify input - if (searchingChecksumsLeft.Remove(Checksum)) - result[Checksum] = this; - - if (compilationState.FrozenSourceGeneratedDocumentStates.HasValue) + if (assetPath.IncludeSolutionCompilationState) { - Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); + if (assetPath.IncludeSolutionCompilationStateChecksums && searchingChecksumsLeft.Remove(this.Checksum)) + onAssetFound(this.Checksum, this, arg); - // This could either be the checksum for the text (which we'll use our regular helper for first)... - await ChecksumCollection.FindAsync(compilationState.FrozenSourceGeneratedDocumentStates.Value, assetHint.DocumentId, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + if (assetPath.IncludeSolutionSourceGeneratorExecutionVersionMap && searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) + onAssetFound(this.SourceGeneratorExecutionVersionMap, compilationState.SourceGeneratorExecutionVersionMap, arg); - // ... or one of the identities. In this case, we'll use the fact that there's a 1:1 correspondence between the - // two collections we hold onto. - for (var i = 0; i < FrozenSourceGeneratedDocumentIdentities.Value.Count; i++) + if (compilationState.FrozenSourceGeneratedDocumentStates != null) { - var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; - if (searchingChecksumsLeft.Remove(identityChecksum)) + Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); + Contract.ThrowIfFalse(FrozenSourceGeneratedDocuments.HasValue); + + // This could either be the checksum for the text (which we'll use our regular helper for first)... + if (assetPath.IncludeSolutionFrozenSourceGeneratedDocumentText) + { + await ChecksumCollection.FindAsync( + new AssetPath(AssetPathKind.DocumentText, assetPath.ProjectId, assetPath.DocumentId), + compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); + } + + // ... or one of the identities. In this case, we'll use the fact that there's a 1:1 correspondence between the + // two collections we hold onto. + if (assetPath.IncludeSolutionFrozenSourceGeneratedDocumentIdentities) { - var id = FrozenSourceGeneratedDocuments!.Value.Ids[i]; - Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.Value.TryGetState(id, out var state)); - result[identityChecksum] = state.Identity; + var documentId = assetPath.DocumentId; + if (documentId != null) + { + // If the caller is asking for a specific document, we can just look it up directly. + var index = FrozenSourceGeneratedDocuments.Value.Ids.IndexOf(documentId); + if (index >= 0) + { + var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value.Children[index]; + if (searchingChecksumsLeft.Remove(identityChecksum)) + { + Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(documentId, out var state)); + onAssetFound(identityChecksum, state.Identity, arg); + } + } + } + else + { + // Otherwise, we'll have to search through all of them. + for (var i = 0; i < FrozenSourceGeneratedDocumentIdentities.Value.Count; i++) + { + var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; + if (searchingChecksumsLeft.Remove(identityChecksum)) + { + var id = FrozenSourceGeneratedDocuments.Value.Ids[i]; + Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); + onAssetFound(identityChecksum, state.Identity, arg); + } + } + } } } } @@ -130,13 +190,13 @@ public async Task FindAsync( // If we're not in a project cone, start the search at the top most state-checksum corresponding to the // entire solution. Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(out var solutionChecksums)); - await solutionChecksums.FindAsync(solutionState, projectCone, assetHint, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } else { // Otherwise, grab the top-most state checksum for this cone and search within that. Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(projectCone.RootProjectId, out var solutionChecksums)); - await solutionChecksums.FindAsync(solutionState, projectCone, assetHint, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } } @@ -146,7 +206,7 @@ public async Task FindAsync( internal sealed class SolutionStateChecksums( ProjectId? projectConeId, Checksum attributes, - ChecksumsAndIds projects, + ProjectChecksumsAndIds projects, ChecksumCollection analyzerReferences) { private ProjectCone? _projectCone; @@ -161,7 +221,7 @@ internal sealed class SolutionStateChecksums( public ProjectId? ProjectConeId { get; } = projectConeId; public Checksum Attributes { get; } = attributes; - public ChecksumsAndIds Projects { get; } = projects; + public ProjectChecksumsAndIds Projects { get; } = projects; public ChecksumCollection AnalyzerReferences { get; } = analyzerReferences; // Acceptably not threadsafe. ProjectCone is a class, and the runtime guarantees anyone will see this field fully @@ -199,90 +259,78 @@ public static SolutionStateChecksums Deserialize(ObjectReader reader) var result = new SolutionStateChecksums( projectConeId: reader.ReadBoolean() ? ProjectId.ReadFrom(reader) : null, attributes: Checksum.ReadFrom(reader), - projects: ChecksumsAndIds.ReadFrom(reader), + projects: ProjectChecksumsAndIds.ReadFrom(reader), analyzerReferences: ChecksumCollection.ReadFrom(reader)); Contract.ThrowIfFalse(result.Checksum == checksum); return result; } - public async Task FindAsync( + public async Task FindAsync( SolutionState solution, ProjectCone? projectCone, - AssetHint assetHint, + AssetPath assetPath, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (searchingChecksumsLeft.Count == 0) return; - // verify input - if (searchingChecksumsLeft.Remove(Checksum)) - result[Checksum] = this; + if (assetPath.IncludeSolutionState) + { + if (assetPath.IncludeSolutionStateChecksums && searchingChecksumsLeft.Remove(Checksum)) + onAssetFound(Checksum, this, arg); - if (searchingChecksumsLeft.Remove(Attributes)) - result[Attributes] = solution.SolutionAttributes; + if (assetPath.IncludeSolutionAttributes && searchingChecksumsLeft.Remove(Attributes)) + onAssetFound(Attributes, solution.SolutionAttributes, arg); - ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); + if (assetPath.IncludeSolutionAnalyzerReferences) + ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); + } if (searchingChecksumsLeft.Count == 0) return; - if (assetHint.ProjectId != null) + if (assetPath.IncludeProjects || assetPath.IncludeDocuments) { - Contract.ThrowIfTrue( - projectCone != null && !projectCone.Contains(assetHint.ProjectId), - "Requesting an asset outside of the cone explicitly being asked for!"); - - var projectState = solution.GetProjectState(assetHint.ProjectId); - if (projectState != null && - projectState.TryGetStateChecksums(out var projectStateChecksums)) + if (assetPath.ProjectId is not null) { - await projectStateChecksums.FindAsync(projectState, assetHint.DocumentId, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + // Dive into this project to search for the remaining checksums. + Contract.ThrowIfTrue( + projectCone != null && !projectCone.Contains(assetPath.ProjectId), + "Requesting an asset outside of the cone explicitly being asked for!"); + + var projectState = solution.GetProjectState(assetPath.ProjectId); + if (projectState != null && + projectState.TryGetStateChecksums(out var projectStateChecksums)) + { + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); + } } - } - else - { - Contract.ThrowIfTrue(assetHint.DocumentId != null); - - // Before doing a depth-first-search *into* each project, first run across all the project at their top - // level. This ensures that when we are trying to sync the projects referenced by a SolutionStateChecksums' - // instance that we don't unnecessarily walk all documents looking just for those. - - foreach (var (projectId, projectState) in solution.ProjectStates) + else { - if (searchingChecksumsLeft.Count == 0) - break; + // Check all projects for the remaining checksums. - // If we're syncing a project cone, no point at all at looking at child projects of the solution that - // are not in that cone. - if (projectCone != null && !projectCone.Contains(projectId)) - continue; - - if (projectState.TryGetStateChecksums(out var projectStateChecksums) && - searchingChecksumsLeft.Remove(projectStateChecksums.Checksum)) + foreach (var (projectId, projectState) in solution.ProjectStates) { - result[projectStateChecksums.Checksum] = projectStateChecksums; - } - } + cancellationToken.ThrowIfCancellationRequested(); - // Now actually do the depth first search into each project. + // If we have no more checksums, can immediately bail out. + if (searchingChecksumsLeft.Count == 0) + break; - foreach (var (projectId, projectState) in solution.ProjectStates) - { - if (searchingChecksumsLeft.Count == 0) - break; - - // If we're syncing a project cone, no point at all at looking at child projects of the solution that - // are not in that cone. - if (projectCone != null && !projectCone.Contains(projectId)) - continue; - - // It's possible not all all our projects have checksums. Specifically, we may have only been asked to - // compute the checksum tree for a subset of projects that were all that a feature needed. - if (projectState.TryGetStateChecksums(out var projectStateChecksums)) - await projectStateChecksums.FindAsync(projectState, hintDocument: null, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + if (projectCone != null && !projectCone.Contains(projectId)) + continue; + + // It's possible not all all our projects have checksums. Specifically, we may have only been asked to + // compute the checksum tree for a subset of projects that were all that a feature needed. + if (!projectState.TryGetStateChecksums(out var projectStateChecksums)) + continue; + + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); + } } } } @@ -296,9 +344,9 @@ internal sealed class ProjectStateChecksums( ChecksumCollection projectReferenceChecksums, ChecksumCollection metadataReferenceChecksums, ChecksumCollection analyzerReferenceChecksums, - ChecksumsAndIds documentChecksums, - ChecksumsAndIds additionalDocumentChecksums, - ChecksumsAndIds analyzerConfigDocumentChecksums) : IEquatable + DocumentChecksumsAndIds documentChecksums, + DocumentChecksumsAndIds additionalDocumentChecksums, + DocumentChecksumsAndIds analyzerConfigDocumentChecksums) : IEquatable { public Checksum Checksum { get; } = Checksum.Create(stackalloc[] { @@ -323,9 +371,9 @@ internal sealed class ProjectStateChecksums( public ChecksumCollection MetadataReferences => metadataReferenceChecksums; public ChecksumCollection AnalyzerReferences => analyzerReferenceChecksums; - public ChecksumsAndIds Documents => documentChecksums; - public ChecksumsAndIds AdditionalDocuments => additionalDocumentChecksums; - public ChecksumsAndIds AnalyzerConfigDocuments => analyzerConfigDocumentChecksums; + public DocumentChecksumsAndIds Documents => documentChecksums; + public DocumentChecksumsAndIds AdditionalDocuments => additionalDocumentChecksums; + public DocumentChecksumsAndIds AnalyzerConfigDocuments => analyzerConfigDocumentChecksums; public override bool Equals(object? obj) => Equals(obj as ProjectStateChecksums); @@ -345,9 +393,9 @@ public void AddAllTo(HashSet checksums) this.ProjectReferences.AddAllTo(checksums); this.MetadataReferences.AddAllTo(checksums); this.AnalyzerReferences.AddAllTo(checksums); - this.Documents.Checksums.AddAllTo(checksums); - this.AdditionalDocuments.Checksums.AddAllTo(checksums); - this.AnalyzerConfigDocuments.Checksums.AddAllTo(checksums); + this.Documents.AddAllTo(checksums); + this.AdditionalDocuments.AddAllTo(checksums); + this.AnalyzerConfigDocuments.AddAllTo(checksums); } public void Serialize(ObjectWriter writer) @@ -378,18 +426,19 @@ public static ProjectStateChecksums Deserialize(ObjectReader reader) projectReferenceChecksums: ChecksumCollection.ReadFrom(reader), metadataReferenceChecksums: ChecksumCollection.ReadFrom(reader), analyzerReferenceChecksums: ChecksumCollection.ReadFrom(reader), - documentChecksums: ChecksumsAndIds.ReadFrom(reader), - additionalDocumentChecksums: ChecksumsAndIds.ReadFrom(reader), - analyzerConfigDocumentChecksums: ChecksumsAndIds.ReadFrom(reader)); + documentChecksums: DocumentChecksumsAndIds.ReadFrom(reader), + additionalDocumentChecksums: DocumentChecksumsAndIds.ReadFrom(reader), + analyzerConfigDocumentChecksums: DocumentChecksumsAndIds.ReadFrom(reader)); Contract.ThrowIfFalse(result.Checksum == checksum); return result; } - public async Task FindAsync( + public async Task FindAsync( ProjectState state, - DocumentId? hintDocument, + AssetPath assetPath, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -400,41 +449,57 @@ public async Task FindAsync( if (searchingChecksumsLeft.Count == 0) return; - if (searchingChecksumsLeft.Remove(Checksum)) + if (assetPath.IncludeProjects) { - result[Checksum] = this; - } + if (assetPath.IncludeProjectStateChecksums && searchingChecksumsLeft.Remove(Checksum)) + onAssetFound(Checksum, this, arg); - // It's normal for callers to just want to sync a single ProjectStateChecksum. So quickly check this, without - // doing all the expensive linear work below if we can bail out early here. - if (searchingChecksumsLeft.Count == 0) - return; + if (assetPath.IncludeProjectAttributes && searchingChecksumsLeft.Remove(Info)) + onAssetFound(Info, state.ProjectInfo.Attributes, arg); - if (searchingChecksumsLeft.Remove(Info)) - { - result[Info] = state.ProjectInfo.Attributes; - } + if (assetPath.IncludeProjectCompilationOptions && searchingChecksumsLeft.Remove(CompilationOptions)) + { + var compilationOptions = state.CompilationOptions ?? throw new InvalidOperationException("We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); + onAssetFound(CompilationOptions, compilationOptions, arg); + } - 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 (assetPath.IncludeProjectParseOptions && searchingChecksumsLeft.Remove(ParseOptions)) + { + var parseOptions = state.ParseOptions ?? throw new InvalidOperationException("We should not be trying to serialize a project with no parse options; RemoteSupportedLanguages.IsSupported should have filtered it out."); + onAssetFound(ParseOptions, parseOptions, arg); + } + + if (assetPath.IncludeProjectProjectReferences) + ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); + + if (assetPath.IncludeProjectMetadataReferences) + ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); + + if (assetPath.IncludeProjectAnalyzerReferences) + ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); } - if (searchingChecksumsLeft.Remove(ParseOptions)) + if (assetPath.IncludeDocuments) { - 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; + await ChecksumCollection.FindAsync(assetPath, state.DocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } - - ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, result, cancellationToken); - ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, result, cancellationToken); - ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); - - await ChecksumCollection.FindAsync(state.DocumentStates, hintDocument, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(state.AdditionalDocumentStates, hintDocument, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(state.AnalyzerConfigDocumentStates, hintDocument, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); } + + public override string ToString() + => $""" + ProjectStateChecksums({ProjectId}) + Info={Info} + CompilationOptions={CompilationOptions} + ParseOptions={ParseOptions} + ProjectReferences={ProjectReferences.Checksum} + MetadataReferences={MetadataReferences.Checksum} + AnalyzerReferences={AnalyzerReferences.Checksum} + Documents={Documents.Checksum} + AdditionalDocuments={AdditionalDocuments.Checksum} + AnalyzerConfigDocuments={AnalyzerConfigDocuments.Checksum} + """; } internal sealed class DocumentStateChecksums( @@ -448,48 +513,36 @@ internal sealed class DocumentStateChecksums( public Checksum Info => infoChecksum; public Checksum Text => textChecksum; - public void Serialize(ObjectWriter writer) - { - // We don't write out the checksum itself as it would bloat the size of this message. If there is corruption - // (which should never ever happen), it will be detected at the project level. - this.DocumentId.WriteTo(writer); - this.Info.WriteTo(writer); - this.Text.WriteTo(writer); - } - - public static DocumentStateChecksums Deserialize(ObjectReader reader) + public void AddAllTo(HashSet checksums) { - return new DocumentStateChecksums( - documentId: DocumentId.ReadFrom(reader), - infoChecksum: Checksum.ReadFrom(reader), - textChecksum: Checksum.ReadFrom(reader)); + checksums.AddIfNotNullChecksum(this.Info); + checksums.AddIfNotNullChecksum(this.Text); } - public async Task FindAsync( + public async Task FindAsync( + AssetPath assetPath, TextDocumentState state, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { Debug.Assert(state.TryGetStateChecksums(out var stateChecksum) && this == stateChecksum); cancellationToken.ThrowIfCancellationRequested(); - if (searchingChecksumsLeft.Remove(Checksum)) - { - result[Checksum] = this; - } + if (assetPath.IncludeDocumentAttributes && searchingChecksumsLeft.Remove(Info)) + onAssetFound(Info, state.Attributes, arg); - if (searchingChecksumsLeft.Remove(Info)) + if (assetPath.IncludeDocumentText && searchingChecksumsLeft.Remove(Text)) { - result[Info] = state.Attributes; - } - - if (searchingChecksumsLeft.Remove(Text)) - { - result[Text] = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); + var text = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); + onAssetFound(Text, text, arg); } } + + public override string ToString() + => $"DocumentStateChecksums({DocumentId})"; } /// @@ -510,11 +563,11 @@ public static ChecksumCollection GetOrCreateChecksumCollection( references, static (references, tuple) => { - using var _ = ArrayBuilder.GetInstance(references.Count, out var checksums); + var checksums = new FixedSizeArrayBuilder(references.Count); foreach (var reference in references) checksums.Add(tuple.serializer.CreateChecksum(reference, tuple.cancellationToken)); - return new ChecksumCollection(checksums.ToImmutableAndClear()); + return new ChecksumCollection(checksums.MoveToImmutable()); }, (serializer, cancellationToken)); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 33a799e996aba..63fa8cb9f446d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -3,9 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; @@ -60,7 +58,7 @@ public TextDocumentState(SolutionServices solutionServices, DocumentInfo info, L info.DocumentServiceProvider, info.Attributes, textAndVersionSource: info.TextLoader != null - ? CreateRecoverableText(info.TextLoader, solutionServices) + ? CreateTextFromLoader(info.TextLoader, PreservationMode.PreserveValue, solutionServices) : CreateStrongText(TextAndVersion.Create(SourceText.From(string.Empty, encoding: null, loadTextOptions.ChecksumAlgorithm), VersionStamp.Default, info.FilePath)), loadTextOptions) { @@ -74,9 +72,6 @@ public TextDocumentState(SolutionServices solutionServices, DocumentInfo info, L private static ITextAndVersionSource CreateStrongText(TextAndVersion text) => new ConstantTextAndVersionSource(text); - private static ITextAndVersionSource CreateStrongText(TextLoader loader) - => new LoadableTextAndVersionSource(loader, cacheResult: true); - private static ITextAndVersionSource CreateRecoverableText(TextAndVersion text, SolutionServices services) { var service = services.GetRequiredService(); @@ -87,18 +82,8 @@ private static ITextAndVersionSource CreateRecoverableText(TextAndVersion text, : new RecoverableTextAndVersion(new ConstantTextAndVersionSource(text), services); } - private static ITextAndVersionSource CreateRecoverableText(TextLoader loader, SolutionServices services) - { - var service = services.GetRequiredService(); - var options = service.Options; - - return options.DisableRecoverableText - ? CreateStrongText(loader) - : new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), services); - } - - public ITemporaryTextStorageInternal? Storage - => (TextAndVersionSource as RecoverableTextAndVersion)?.Storage; + public ITemporaryStorageTextHandle? StorageHandle + => (TextAndVersionSource as RecoverableTextAndVersion)?.StorageHandle; public bool TryGetText([NotNullWhen(returnValue: true)] out SourceText? text) { @@ -178,13 +163,34 @@ public TextDocumentState UpdateText(SourceText newText, PreservationMode mode) public TextDocumentState UpdateText(TextLoader loader, PreservationMode mode) { // don't blow up on non-text documents. - var newTextSource = mode == PreservationMode.PreserveIdentity - ? CreateStrongText(loader) - : CreateRecoverableText(loader, solutionServices); + var newTextSource = CreateTextFromLoader(loader, mode, this.solutionServices); return UpdateText(newTextSource, mode, incremental: false); } + private static ITextAndVersionSource CreateTextFromLoader(TextLoader loader, PreservationMode mode, SolutionServices solutionServices) + { + var service = solutionServices.GetRequiredService(); + var options = service.Options; + + // If the caller is explicitly stating that identity must be preserved, then we created a source that will load + // from the loader the first time, but then cache that result so that hte same result is *always* returned. + if (mode == PreservationMode.PreserveIdentity || options.DisableRecoverableText) + return new LoadableTextAndVersionSource(loader, cacheResult: true); + + // If the loader asks us to always hold onto it strongly, then we do not want to create a recoverable text + // source here. Instead, we'll go back to the loader each time to get the text. This is useful for when the + // loader knows it can always reconstitute the snapshot exactly as it was before. For example, if the loader + // points at the contents of a memory mapped file in another process. + if (loader.AlwaysHoldStrongly) + return new LoadableTextAndVersionSource(loader, cacheResult: false); + + // Otherwise, we just want to hold onto this loader by value. So we create a loader that will load the + // contents, but not hold onto them strongly, and we wrap it in a recoverable-text that will then take those + // contents and dump it into a memory-mapped-file in this process so that snapshot semantics can be preserved. + return new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), solutionServices); + } + protected virtual TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental) { return new TextDocumentState( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs index 9c1513be2405e..3f646f09e1971 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs @@ -36,11 +36,9 @@ private async Task ComputeChecksumsAsync(CancellationTok { using (Logger.LogBlock(FunctionId.DocumentState_ComputeChecksumsAsync, FilePath, cancellationToken)) { - var serializer = solutionServices.GetRequiredService(); - var infoChecksum = this.Attributes.Checksum; var serializableText = await SerializableSourceText.FromTextDocumentStateAsync(this, cancellationToken).ConfigureAwait(false); - var textChecksum = serializer.CreateChecksum(serializableText, cancellationToken); + var textChecksum = serializableText.ContentChecksum; return new DocumentStateChecksums(this.Id, infoChecksum, textChecksum); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 781027bee1db2..d9a24fa7d891a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -3,28 +3,51 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Collections.Immutable; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Shared.Collections; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; +// On NetFx, frozen dictionary is very expensive when you give it a case insensitive comparer. This is due to +// unavoidable allocations it performs while doing its key-analysis that involve going through the non-span-aware +// culture types. So, on netfx, we use a plain ReadOnlyDictionary here. +#if NET +using FilePathToDocumentIds = FrozenDictionary>; +#else +using FilePathToDocumentIds = ReadOnlyDictionary>; +#endif + /// /// Holds on a to map and an ordering. /// -internal readonly struct TextDocumentStates +internal sealed class TextDocumentStates where TState : TextDocumentState { +#if NET + private static readonly ObjectPool>> s_filePathPool = new(() => new(SolutionState.FilePathComparer)); +#endif + public static readonly TextDocumentStates Empty = - new([], ImmutableSortedDictionary.Create(DocumentIdComparer.Instance)); + new([], + ImmutableSortedDictionary.Create(DocumentIdComparer.Instance), +#if NET + FilePathToDocumentIds.Empty); +#else + new(new Dictionary>())); +#endif private readonly ImmutableList _ids; @@ -35,28 +58,36 @@ internal readonly struct TextDocumentStates /// private readonly ImmutableSortedDictionary _map; - private TextDocumentStates(ImmutableList ids, ImmutableSortedDictionary map) + private FilePathToDocumentIds? _filePathToDocumentIds; + + private TextDocumentStates( + ImmutableList ids, + ImmutableSortedDictionary map, + FilePathToDocumentIds? filePathToDocumentIds) { Debug.Assert(map.KeyComparer == DocumentIdComparer.Instance); _ids = ids; _map = map; + _filePathToDocumentIds = filePathToDocumentIds; } public TextDocumentStates(IEnumerable states) : this(states.Select(s => s.Id).ToImmutableList(), - states.ToImmutableSortedDictionary(state => state.Id, state => state, DocumentIdComparer.Instance)) + states.ToImmutableSortedDictionary(state => state.Id, state => state, DocumentIdComparer.Instance), + filePathToDocumentIds: null) { } public TextDocumentStates(IEnumerable infos, Func stateConstructor) : this(infos.Select(info => info.Id).ToImmutableList(), - infos.ToImmutableSortedDictionary(info => info.Id, stateConstructor, DocumentIdComparer.Instance)) + infos.ToImmutableSortedDictionary(info => info.Id, stateConstructor, DocumentIdComparer.Instance), + filePathToDocumentIds: null) { } public TextDocumentStates WithCompilationOrder(ImmutableList ids) - => new(ids, _map); + => new(ids, _map, _filePathToDocumentIds); public int Count => _map.Count; @@ -79,7 +110,7 @@ public TState GetRequiredState(DocumentId documentId) /// /// s in the order in which they were added to the project (the compilation order). /// - public readonly IReadOnlyList Ids => _ids; + public IReadOnlyList Ids => _ids; /// /// States ordered by . @@ -98,47 +129,23 @@ public IEnumerable GetStatesInCompilationOrder() } public ImmutableArray SelectAsArray(Func selector) - { - // Directly use ImmutableArray.Builder as we know the final size - var builder = ImmutableArray.CreateBuilder(_map.Count); - - foreach (var (_, state) in _map) - { - builder.Add(selector(state)); - } - - return builder.MoveToImmutable(); - } + => SelectAsArray( + static (state, selector) => selector(state), + selector); public ImmutableArray SelectAsArray(Func selector, TArg arg) { - // Directly use ImmutableArray.Builder as we know the final size - var builder = ImmutableArray.CreateBuilder(_map.Count); - - foreach (var (_, state) in _map) - { - builder.Add(selector(state, arg)); - } - - return builder.MoveToImmutable(); - } - - public async ValueTask> SelectAsArrayAsync(Func> selector, TArg arg, CancellationToken cancellationToken) - { - // Directly use ImmutableArray.Builder as we know the final size - var builder = ImmutableArray.CreateBuilder(_map.Count); - + var result = new FixedSizeArrayBuilder(_map.Count); foreach (var (_, state) in _map) - { - builder.Add(await selector(state, arg, cancellationToken).ConfigureAwait(true)); - } + result.Add(selector(state, arg)); - return builder.MoveToImmutable(); + return result.MoveToImmutable(); } public TextDocumentStates AddRange(ImmutableArray states) => new(_ids.AddRange(states.Select(state => state.Id)), - _map.AddRange(states.Select(state => KeyValuePairUtil.Create(state.Id, state)))); + _map.AddRange(states.Select(state => KeyValuePairUtil.Create(state.Id, state))), + filePathToDocumentIds: null); public TextDocumentStates RemoveRange(ImmutableArray ids) { @@ -158,22 +165,38 @@ public TextDocumentStates RemoveRange(ImmutableArray ids) } IEnumerable enumerableIds = ids; - return new(_ids.RemoveRange(enumerableIds), _map.RemoveRange(enumerableIds)); + return new(_ids.RemoveRange(enumerableIds), _map.RemoveRange(enumerableIds), filePathToDocumentIds: null); } internal TextDocumentStates SetState(DocumentId id, TState state) - => new(_ids, _map.SetItem(id, state)); + { + var oldState = _map[id]; + var filePathToDocumentIds = oldState.FilePath != state.FilePath + ? null + : _filePathToDocumentIds; + + return new(_ids, _map.SetItem(id, state), filePathToDocumentIds); + } public TextDocumentStates UpdateStates(Func transformation, TArg arg) { var builder = _map.ToBuilder(); - + var filePathsChanged = false; foreach (var (id, state) in _map) { - builder[id] = transformation(state, arg); + var newState = transformation(state, arg); + + // Track if the file path changed when updating any of the state values. + filePathsChanged = filePathsChanged || newState.FilePath != state.FilePath; + + builder[id] = newState; } - return new(_ids, builder.ToImmutable()); + // If any file paths changed, don't pass along our computed map. We'll recompute it on demand when needed. + var filePaths = filePathsChanged + ? null + : _filePathToDocumentIds; + return new(_ids, builder.ToImmutable(), filePaths); } /// @@ -210,13 +233,13 @@ public IEnumerable GetChangedStateIds(TextDocumentStates old /// Returns a s of added documents. /// public IEnumerable GetAddedStateIds(TextDocumentStates oldStates) - => (_ids == oldStates._ids) ? SpecializedCollections.EmptyEnumerable() : Except(_ids, oldStates._map); + => (_ids == oldStates._ids) ? [] : Except(_ids, oldStates._map); /// /// Returns a s of removed documents. /// public IEnumerable GetRemovedStateIds(TextDocumentStates oldStates) - => (_ids == oldStates._ids) ? SpecializedCollections.EmptyEnumerable() : Except(oldStates._ids, _map); + => (_ids == oldStates._ids) ? [] : Except(oldStates._ids, _map); private static IEnumerable Except(ImmutableList ids, ImmutableSortedDictionary map) { @@ -264,10 +287,73 @@ public int Compare(DocumentId? x, DocumentId? y) } } - public async ValueTask> GetChecksumsAndIdsAsync(CancellationToken cancellationToken) + public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) + { + var attributeChecksums = new FixedSizeArrayBuilder(_map.Count); + var textChecksums = new FixedSizeArrayBuilder(_map.Count); + var documentIds = new FixedSizeArrayBuilder(_map.Count); + + foreach (var (documentId, state) in _map) + { + var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + attributeChecksums.Add(stateChecksums.Info); + textChecksums.Add(stateChecksums.Text); + documentIds.Add(documentId); + } + + return new( + new ChecksumCollection(attributeChecksums.MoveToImmutable()), + new ChecksumCollection(textChecksums.MoveToImmutable()), + documentIds.MoveToImmutable()); + } + + public void AddDocumentIdsWithFilePath(ref TemporaryArray temporaryArray, string filePath) { - var documentChecksumTasks = SelectAsArray(static (state, token) => state.GetChecksumAsync(token), cancellationToken); - var documentChecksums = new ChecksumCollection(await documentChecksumTasks.WhenAll().ConfigureAwait(false)); - return new(documentChecksums, SelectAsArray(static s => s.Id)); + // Lazily initialize the file path map if not computed. + _filePathToDocumentIds ??= ComputeFilePathToDocumentIds(); + + if (_filePathToDocumentIds.TryGetValue(filePath, out var oneOrMany)) + { + foreach (var value in oneOrMany) + temporaryArray.Add(value); + } + } + + public DocumentId? GetFirstDocumentIdWithFilePath(string filePath) + { + // Lazily initialize the file path map if not computed. + _filePathToDocumentIds ??= ComputeFilePathToDocumentIds(); + + // Safe to call .First here as the values in the _filePathToDocumentIds dictionary will never empty. + return _filePathToDocumentIds.TryGetValue(filePath, out var oneOrMany) + ? oneOrMany.First() + : null; + } + + private FilePathToDocumentIds ComputeFilePathToDocumentIds() + { +#if NET + using var pooledDictionary = s_filePathPool.GetPooledObject(); + var result = pooledDictionary.Object; +#else + var result = new Dictionary>(SolutionState.FilePathComparer); +#endif + + foreach (var (documentId, state) in _map) + { + var filePath = state.FilePath; + if (filePath is null) + continue; + + result[filePath] = result.TryGetValue(filePath, out var existingValue) + ? existingValue.Add(documentId) + : OneOrMany.Create(documentId); + } + +#if NET + return result.ToFrozenDictionary(SolutionState.FilePathComparer); +#else + return new(result); +#endif } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs index e771796d35fac..cf625034f68d8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs @@ -31,6 +31,18 @@ public abstract class TextLoader internal virtual string? FilePath => null; + /// + /// if the document that holds onto this loader should do so with a strong reference, versus + /// a reference that will take the contents of this loader and store them in a recoverable form (e.g. a memory + /// mapped file within the same process). This should be used when the underlying data is already stored + /// in a recoverable form somewhere else and it would be wasteful to store another copy. For example, a document + /// that is backed by memory-mapped contents in another process does not need to dump it's content to + /// another memory-mapped file in the process it lives in. It can always recover the text from the original + /// process. + /// + internal virtual bool AlwaysHoldStrongly + => false; + /// /// True if reloads from its original binary representation (e.g. file on disk). /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITextAndVersionSource.cs similarity index 83% rename from src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITextAndVersionSource.cs index ab0bc21554dde..ac7f86ac6a84e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITextAndVersionSource.cs @@ -16,6 +16,12 @@ internal interface ITextAndVersionSource /// bool CanReloadText { get; } + /// + /// Retrieves the underlying if that's what this was + /// created from and still has access to. + /// + TextLoader? TextLoader { get; } + bool TryGetValue(LoadTextOptions options, [MaybeNullWhen(false)] out TextAndVersion value); TextAndVersion GetValue(LoadTextOptions options, CancellationToken cancellationToken); Task GetValueAsync(LoadTextOptions options, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs new file mode 100644 index 0000000000000..75e0d0c670b33 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis; + +/// +/// Similar to , but for trees. Allows hiding (or introspecting) the details of how +/// a tree is created for a particular document. +/// +internal interface ITreeAndVersionSource +{ + Task GetValueAsync(CancellationToken cancellationToken); + TreeAndVersion GetValue(CancellationToken cancellationToken); + bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/LoadableTextAndVersionSource.cs similarity index 95% rename from src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/LoadableTextAndVersionSource.cs index 70417d9b9dbad..28a1f3f6fe04f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/LoadableTextAndVersionSource.cs @@ -35,10 +35,10 @@ private sealed class LazyValueWithOptions(LoadableTextAndVersionSource source, L private WeakReference? _weakInstance; private Task LoadAsync(CancellationToken cancellationToken) - => Source.Loader.LoadTextAsync(Options, cancellationToken); + => Source.TextLoader.LoadTextAsync(Options, cancellationToken); private TextAndVersion LoadSynchronously(CancellationToken cancellationToken) - => Source.Loader.LoadTextSynchronously(Options, cancellationToken); + => Source.TextLoader.LoadTextSynchronously(Options, cancellationToken); public bool TryGetValue([MaybeNullWhen(false)] out TextAndVersion value) { @@ -102,13 +102,13 @@ private void UpdateWeakAndStrongReferences_NoLock(TextAndVersion textAndVersion) } } - public readonly TextLoader Loader = loader; + public TextLoader TextLoader { get; } = loader; public readonly bool CacheResult = cacheResult; private LazyValueWithOptions? _lazyValue; public bool CanReloadText - => Loader.CanReloadText; + => TextLoader.CanReloadText; private LazyValueWithOptions GetLazyValue(LoadTextOptions options) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.RecoverableText.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs similarity index 80% rename from src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.RecoverableText.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs index 1e996fc9eb57b..f8ded8d24ae96 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.RecoverableText.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -23,8 +24,11 @@ internal sealed partial class RecoverableTextAndVersion private sealed partial class RecoverableText { // enforce saving in a queue so save's don't overload the thread pool. - private static Task s_latestTask = Task.CompletedTask; - private static readonly NonReentrantLock s_taskGuard = new(); + private static readonly AsyncBatchingWorkQueue<(RecoverableText recoverableText, SourceText sourceText)> s_saveQueue = + new(TimeSpan.Zero, + SaveAllAsync, + AsynchronousOperationListenerProvider.NullListener, + CancellationToken.None); /// /// Lazily created. Access via the property. @@ -136,27 +140,16 @@ private void UpdateWeakReferenceAndEnqueueSaveTask_NoLock(SourceText instance) if (!_saved) { _saved = true; - using (s_taskGuard.DisposableWait()) - { - // force all save tasks to be in sequence so we don't hog all the threads. - s_latestTask = s_latestTask.SafeContinueWithFromAsync(async _ => - { - // Now defer to our subclass to actually save the instance to secondary storage. - await SaveAsync(instance, CancellationToken.None).ConfigureAwait(false); - - // Only set _initialValue to null if the saveTask completed successfully. If the save did not complete, - // we want to keep it around to service future requests. Once we do clear out this value, then all - // future request will either retrieve the value from the weak reference (if anyone else is holding onto - // it), or will recover from underlying storage. - _initialValue = null; - }, - CancellationToken.None, - // Ensure we run continuations asynchronously so that we don't start running the continuation while - // holding s_taskGuard. - TaskContinuationOptions.RunContinuationsAsynchronously, - TaskScheduler.Default); - } + + s_saveQueue.AddWork((this, instance)); } } + + private static async ValueTask SaveAllAsync( + ImmutableSegmentedList<(RecoverableText recoverableText, SourceText sourceText)> list, CancellationToken cancellationToken) + { + foreach (var (recoverableText, sourceText) in list) + await recoverableText.SaveAsync(sourceText, cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.cs similarity index 83% rename from src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.cs index 392acdfbfec8c..7314888e4c9ba 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.cs @@ -17,7 +17,6 @@ namespace Microsoft.CodeAnalysis; /// internal sealed partial class RecoverableTextAndVersion(ITextAndVersionSource initialSource, SolutionServices services) : ITextAndVersionSource { - // Starts as ITextAndVersionSource and is replaced with RecoverableText when the TextAndVersion value is requested. // At that point the initial source is no longer referenced and can be garbage collected. private object _initialSourceOrRecoverableText = initialSource; @@ -43,8 +42,14 @@ private bool TryGetInitialSourceOrRecoverableText([NotNullWhen(true)] out ITextA return false; } - public ITemporaryTextStorageInternal? Storage - => (_initialSourceOrRecoverableText as RecoverableText)?.Storage; + /// + /// Attempt to return the original loader if we still have it. + /// + public TextLoader? TextLoader + => (_initialSourceOrRecoverableText as ITextAndVersionSource)?.TextLoader; + + public ITemporaryStorageTextHandle? StorageHandle + => (_initialSourceOrRecoverableText as RecoverableText)?.StorageHandle; public bool TryGetValue(LoadTextOptions options, [MaybeNullWhen(false)] out TextAndVersion value) { @@ -138,7 +143,7 @@ private sealed partial class RecoverableText public readonly ITextAndVersionSource? InitialSource; public readonly LoadTextOptions LoadTextOptions; - public ITemporaryTextStorageInternal? _storage; + public ITemporaryStorageTextHandle? _storageHandle; public RecoverableText(ITextAndVersionSource source, TextAndVersion textAndVersion, LoadTextOptions options, SolutionServices services) { @@ -161,37 +166,42 @@ public RecoverableText(ITextAndVersionSource source, TextAndVersion textAndVersi public TextAndVersion ToTextAndVersion(SourceText text) => TextAndVersion.Create(text, Version, LoadDiagnostic); - public ITemporaryTextStorageInternal? Storage => _storage; + public ITemporaryStorageTextHandle? StorageHandle => _storageHandle; private async Task RecoverAsync(CancellationToken cancellationToken) { - Contract.ThrowIfNull(_storage); + Contract.ThrowIfNull(_storageHandle); using (Logger.LogBlock(FunctionId.Workspace_Recoverable_RecoverTextAsync, cancellationToken)) { - return await _storage.ReadTextAsync(cancellationToken).ConfigureAwait(false); + return await _storageHandle.ReadFromTemporaryStorageAsync(cancellationToken).ConfigureAwait(false); } } private SourceText Recover(CancellationToken cancellationToken) { - Contract.ThrowIfNull(_storage); + Contract.ThrowIfNull(_storageHandle); using (Logger.LogBlock(FunctionId.Workspace_Recoverable_RecoverText, cancellationToken)) { - return _storage.ReadText(cancellationToken); + return _storageHandle.ReadFromTemporaryStorage(cancellationToken); } } private async Task SaveAsync(SourceText text, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(_storage == null); // Cannot save more than once + Contract.ThrowIfFalse(_storageHandle == null); // Cannot save more than once + + var handle = await _storageService.WriteToTemporaryStorageAsync(text, cancellationToken).ConfigureAwait(false); - var storage = _storageService.CreateTemporaryTextStorage(); - await storage.WriteTextAsync(text, cancellationToken).ConfigureAwait(false); + // make sure write is done before setting _storageHandle field + Interlocked.CompareExchange(ref _storageHandle, handle, null); - // make sure write is done before setting _storage field - Interlocked.CompareExchange(ref _storage, storage, null); + // Only set _initialValue to null once writing to the storage service completes fully. If the save did not + // complete, we want to keep it around to service future requests. Once we do clear out this value, then + // all future request will either retrieve the value from the weak reference (if anyone else is holding onto + // it), or will recover from underlying storage. + _initialValue = null; } public bool TryGetTextVersion(LoadTextOptions options, out VersionStamp version) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs new file mode 100644 index 0000000000000..46be64b1bd3ec --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis; + +/// +/// Simple implementation of backed by an opaque ."/> +/// +internal sealed class SimpleTreeAndVersionSource : ITreeAndVersionSource +{ + private readonly AsyncLazy _source; + + private SimpleTreeAndVersionSource(AsyncLazy source) + { + _source = source; + } + + public Task GetValueAsync(CancellationToken cancellationToken) + => _source.GetValueAsync(cancellationToken); + + public TreeAndVersion GetValue(CancellationToken cancellationToken) + => _source.GetValue(cancellationToken); + + public bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value) + => _source.TryGetValue(out value); + + public static SimpleTreeAndVersionSource Create( + Func> asynchronousComputeFunction, + Func? synchronousComputeFunction, TArg arg) + { + return new(AsyncLazy.Create(asynchronousComputeFunction, synchronousComputeFunction, arg)); + } + + public static SimpleTreeAndVersionSource Create(TreeAndVersion source) + => new(AsyncLazy.Create(source)); +} diff --git a/src/Workspaces/Core/Portable/Workspace/SourceGeneratorExecution.cs b/src/Workspaces/Core/Portable/Workspace/SourceGeneratorExecution.cs new file mode 100644 index 0000000000000..500c1abdde35c --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/SourceGeneratorExecution.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more 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.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +internal enum SourceGeneratorExecutionPreference +{ + /// + /// Source generators should re-run after any change to a project. + /// + Automatic, + + /// + /// Source generators should re-run only when certain changes happen. The set of things is host dependent, but + /// generally should be things like "builds" or "file saves". Larger events (not just text changes) which indicate + /// that it's a more reasonable time to run generators. + /// + Balanced, +} + +internal static class SourceGeneratorExecutionPreferenceUtilities +{ + private const string automatic = "automatic"; + private const string balanced = "balanced"; + + // Default to beginning_of_line if we don't know the value. + public static string GetEditorConfigString(SourceGeneratorExecutionPreference? value) + { + return value switch + { + SourceGeneratorExecutionPreference.Automatic => automatic, + SourceGeneratorExecutionPreference.Balanced => balanced, + null => "", + _ => throw ExceptionUtilities.UnexpectedValue(value), + }; + } + + public static SourceGeneratorExecutionPreference? Parse( + string optionString) + { + return optionString switch + { + automatic => SourceGeneratorExecutionPreference.Automatic, + balanced => SourceGeneratorExecutionPreference.Balanced, + _ => null, + }; + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs b/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs index d3251442bd3e4..da91f2c6f1a09 100644 --- a/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs +++ b/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs @@ -22,18 +22,16 @@ public static ImmutableArray GetRelatedDocumentsWithChanges(this Sourc { var documentId = workspace.GetDocumentIdInCurrentContext(text.Container); if (documentId == null) - { return []; - } var solution = workspace.CurrentSolution; - if (workspace.TryGetOpenSourceGeneratedDocumentIdentity(documentId, out var documentIdentity)) + if (workspace.TryGetOpenSourceGeneratedDocumentIdentity(documentId, out var identityAndDateTime)) { // 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 [solution.WithFrozenSourceGeneratedDocument(documentIdentity, text)]; + return [solution.WithFrozenSourceGeneratedDocument(identityAndDateTime.identity, identityAndDateTime.generationDateTime, text)]; } var relatedIds = solution.GetRelatedDocumentIds(documentId); @@ -65,14 +63,10 @@ public static ImmutableArray GetRelatedDocumentsWithChanges(this Sourc var solution = workspace.CurrentSolution; var id = workspace.GetDocumentIdInCurrentContext(text.Container); if (id == null) - { return null; - } - if (workspace.TryGetOpenSourceGeneratedDocumentIdentity(id, out var documentIdentity)) - { - return solution.WithFrozenSourceGeneratedDocument(documentIdentity, text); - } + if (workspace.TryGetOpenSourceGeneratedDocumentIdentity(id, out var identityAndDateTime)) + return solution.WithFrozenSourceGeneratedDocument(identityAndDateTime.identity, identityAndDateTime.generationDateTime, text); if (solution.ContainsDocument(id)) { diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index b75aad7b448bc..201d28946e432 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -85,7 +86,15 @@ protected Workspace(HostServices host, string? workspaceKind) var emptyOptions = new SolutionOptionSet(_legacyOptions); - _latestSolution = CreateSolution(info, emptyOptions, analyzerReferences: SpecializedCollections.EmptyReadOnlyList()); + _latestSolution = CreateSolution(info, emptyOptions, analyzerReferences: []); + + _updateSourceGeneratorsQueue = new AsyncBatchingWorkQueue<(ProjectId? projectId, bool forceRegeneration)>( + // Idle processing speed + TimeSpan.FromMilliseconds(1500), + ProcessUpdateSourceGeneratorRequestAsync, + EqualityComparer<(ProjectId? projectId, bool forceRegeneration)>.Default, + listenerProvider.GetListener(), + _updateSourceGeneratorsQueueTokenSource.Token); } /// @@ -283,8 +292,12 @@ static Solution UnifyLinkedDocumentContents(Solution oldSolution, Solution newSo if (!addedProject.SupportsCompilation) continue; + // It's likely when adding files that if we link them to files in another project, that we will do the + // same for other sibling files being added. Keep that information around so help speed up the linked + // file search as we process siblings. + ProjectId? relatedProjectIdHint = null; foreach (var addedDocument in addedProject.Documents) - newSolution = UpdateAddedDocumentToExistingContentsInSolution(newSolution, addedDocument.Id); + (newSolution, relatedProjectIdHint) = UpdateAddedDocumentToExistingContentsInSolution(newSolution, addedDocument.Id, relatedProjectIdHint); } using var _ = PooledHashSet.GetInstance(out var seenChangedDocuments); @@ -296,8 +309,9 @@ static Solution UnifyLinkedDocumentContents(Solution oldSolution, Solution newSo continue; // Now do the same for all added documents in a project. + ProjectId? relatedProjectIdHint = null; foreach (var addedDocument in projectChanges.GetAddedDocuments()) - newSolution = UpdateAddedDocumentToExistingContentsInSolution(newSolution, addedDocument); + (newSolution, relatedProjectIdHint) = UpdateAddedDocumentToExistingContentsInSolution(newSolution, addedDocument, relatedProjectIdHint); // now, for any changed document, ensure we go and make all links to it have the same text/tree. foreach (var changedDocumentId in projectChanges.GetChangedDocuments()) @@ -307,16 +321,33 @@ static Solution UnifyLinkedDocumentContents(Solution oldSolution, Solution newSo return newSolution; } - static Solution UpdateAddedDocumentToExistingContentsInSolution(Solution solution, DocumentId addedDocumentId) + static (Solution newSolution, ProjectId? relatedProjectId) UpdateAddedDocumentToExistingContentsInSolution( + Solution solution, DocumentId addedDocumentId, ProjectId? relatedProjectIdHint) { - var relatedDocumentIds = solution.GetRelatedDocumentIds(addedDocumentId); - foreach (var relatedDocumentId in relatedDocumentIds) - { - var relatedDocument = solution.GetRequiredDocument(relatedDocumentId); - return solution.WithDocumentContentsFrom(addedDocumentId, relatedDocument.DocumentState, forceEvenIfTreesWouldDiffer: false); - } + Contract.ThrowIfTrue(addedDocumentId.ProjectId == relatedProjectIdHint); - return solution; + // Look for a related document we can create our contents from. We only have to look for a single related + // doc as we'll be done once we update our contents to theirs. Note: GetFirstRelatedDocumentId will also + // not search the project that addedDocumentId came from. So this will help ensure we don't repeatedly add + // documents to a project, then look for related docs *within that project*, forcing the file-path map in it + // to be recreated for each document. + var relatedDocumentId = solution.GetFirstRelatedDocumentId(addedDocumentId, relatedProjectIdHint); + + // Couldn't find a related document. Keep the same solution, and keep track of the best related project we + // found while processing this project. + if (relatedDocumentId is null) + return (solution, relatedProjectIdHint); + + var relatedDocument = solution.GetRequiredDocument(relatedDocumentId); + + // Should never return a file as its own related document + Contract.ThrowIfTrue(relatedDocumentId == addedDocumentId); + + // Related document must come from a distinct project. + Contract.ThrowIfTrue(relatedDocumentId.ProjectId == addedDocumentId.ProjectId); + + var newSolution = solution.WithDocumentContentsFrom(addedDocumentId, relatedDocument.DocumentState, forceEvenIfTreesWouldDiffer: false); + return (newSolution, relatedProjectId: relatedDocumentId.ProjectId); } static Solution UpdateExistingDocumentsToChangedDocumentContents(Solution solution, DocumentId changedDocumentId, HashSet processedDocuments) @@ -329,6 +360,9 @@ static Solution UpdateExistingDocumentsToChangedDocumentContents(Solution soluti var relatedDocumentIds = solution.GetRelatedDocumentIds(changedDocumentId); foreach (var relatedDocumentId in relatedDocumentIds) { + if (relatedDocumentId == changedDocumentId) + continue; + if (processedDocuments.Add(relatedDocumentId)) solution = solution.WithDocumentContentsFrom(relatedDocumentId, changedDocument.DocumentState, forceEvenIfTreesWouldDiffer: false); } @@ -595,10 +629,25 @@ protected virtual void Dispose(bool finalize) { disposableService.Dispose(); } + + // We're disposing this workspace. Stop any work to update SG docs in the background. + _updateSourceGeneratorsQueueTokenSource.Cancel(); } #region Host API + private static Solution CheckAndAddProjects(Solution solution, IReadOnlyList projects) + { + using var _ = ArrayBuilder.GetInstance(projects.Count, out var builder); + foreach (var project in projects) + { + CheckProjectIsNotInSolution(solution, project.Id); + builder.Add(project); + } + + return solution.AddProjects(builder); + } + private static Solution CheckAndAddProject(Solution newSolution, ProjectInfo project) { CheckProjectIsNotInSolution(newSolution, project.Id); @@ -617,8 +666,7 @@ protected internal void OnSolutionAdded(SolutionInfo solutionInfo) var newSolution = this.CreateSolution(solutionInfo); - foreach (var project in solutionInfo.Projects) - newSolution = CheckAndAddProject(newSolution, project); + newSolution = CheckAndAddProjects(newSolution, solutionInfo.Projects); return newSolution; }, WorkspaceChangeKind.SolutionAdded); @@ -634,8 +682,7 @@ protected internal void OnSolutionReloaded(SolutionInfo reloadedSolutionInfo) { var newSolution = this.CreateSolution(reloadedSolutionInfo); - foreach (var project in reloadedSolutionInfo.Projects) - newSolution = CheckAndAddProject(newSolution, project); + newSolution = CheckAndAddProjects(newSolution, reloadedSolutionInfo.Projects); return this.AdjustReloadedSolution(oldSolution, newSolution); }, WorkspaceChangeKind.SolutionReloaded); diff --git a/src/Workspaces/Core/Portable/Workspace/WorkspaceKind.cs b/src/Workspaces/Core/Portable/Workspace/WorkspaceKind.cs index 86f36f622bfa5..81280aab6c2bd 100644 --- a/src/Workspaces/Core/Portable/Workspace/WorkspaceKind.cs +++ b/src/Workspaces/Core/Portable/Workspace/WorkspaceKind.cs @@ -24,5 +24,6 @@ public static class WorkspaceKind internal const string CloudEnvironmentClientWorkspace = nameof(CloudEnvironmentClientWorkspace); internal const string RemoteWorkspace = nameof(RemoteWorkspace); + internal const string SemanticSearch = nameof(SemanticSearch); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs index 811d5f448be43..f59a4743aa37c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs @@ -39,7 +39,7 @@ public abstract partial class Workspace private readonly Dictionary _textTrackers = []; private readonly Dictionary _documentToAssociatedBufferMap = []; - private readonly Dictionary _openSourceGeneratedDocumentIdentities = []; + private readonly Dictionary _openSourceGeneratedDocumentIdentities = []; /// /// True if this workspace supports manually opening and closing documents. @@ -195,9 +195,7 @@ public virtual IEnumerable GetOpenDocumentIds(ProjectId? projectId = using (_stateLock.DisposableWait()) { if (_projectToOpenDocumentsMap.Count == 0) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (projectId != null) { @@ -206,7 +204,7 @@ public virtual IEnumerable GetOpenDocumentIds(ProjectId? projectId = return documentIds; } - return SpecializedCollections.EmptyEnumerable(); + return []; } return _projectToOpenDocumentsMap.SelectManyAsArray(kvp => kvp.Value); @@ -287,12 +285,10 @@ 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) + internal bool TryGetOpenSourceGeneratedDocumentIdentity(DocumentId id, out (SourceGeneratedDocumentIdentity identity, DateTime generationDateTime) documentIdentity) { using (_serializationLock.DisposableWait()) - { return _openSourceGeneratedDocumentIdentities.TryGetValue(id, out documentIdentity); - } } /// @@ -456,7 +452,7 @@ internal void OnSourceGeneratedDocumentOpened( AddToOpenDocumentMap(documentId); _documentToAssociatedBufferMap.Add(documentId, textContainer); - _openSourceGeneratedDocumentIdentities.Add(documentId, document.Identity); + _openSourceGeneratedDocumentIdentities.Add(documentId, (document.Identity, document.GenerationDateTime)); UpdateCurrentContextMapping_NoLock(textContainer, documentId, isCurrentContext: true); diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_SourceGeneration.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_SourceGeneration.cs new file mode 100644 index 0000000000000..2d7ea17c29427 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_SourceGeneration.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.Collections.Frozen; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis; + +public partial class Workspace +{ + /// + /// Used for batching up a lot of events and only combining them into a single request to update generators. The + /// represents the projects that have changed, and which need their source-generators + /// re-run. in the list indicates the entire solution has changed and all generators need to + /// be rerun. The represents if source generators should be fully rerun for the requested + /// project or solution. If , the existing generator driver will be used, which may result + /// in no actual changes to emitted source (as the driver may decide no inputs changed, and thus all outputs should + /// be reused). If , the existing driver will be dropped, forcing all generation to be redone. + /// + private readonly AsyncBatchingWorkQueue<(ProjectId? projectId, bool forceRegeneration)> _updateSourceGeneratorsQueue; + + private readonly CancellationTokenSource _updateSourceGeneratorsQueueTokenSource = new(); + + internal void EnqueueUpdateSourceGeneratorVersion(ProjectId? projectId, bool forceRegeneration) + => _updateSourceGeneratorsQueue.AddWork((projectId, forceRegeneration)); + + private async ValueTask ProcessUpdateSourceGeneratorRequestAsync( + ImmutableSegmentedList<(ProjectId? projectId, bool forceRegeneration)> projectIds, CancellationToken cancellationToken) + { + var configuration = this.Services.GetRequiredService().Options; + if (configuration.SourceGeneratorExecution is SourceGeneratorExecutionPreference.Automatic) + { + // If we're in automatic mode, we don't need to do anything *unless* the host has asked us to + // force-regenerate something. In that case we're literally going to drop our generator drivers and + // regenerate the code, so we can't depend on automatic running generators normally. + if (!projectIds.Any(t => t.forceRegeneration)) + return; + } + + // Ensure we're fully loaded before rerunning generators. + var workspaceStatusService = this.Services.GetRequiredService(); + await workspaceStatusService.WaitUntilFullyLoadedAsync(cancellationToken).ConfigureAwait(false); + + await this.SetCurrentSolutionAsync( + useAsync: true, + oldSolution => + { + var updates = GetUpdatedSourceGeneratorVersions(oldSolution, projectIds); + return oldSolution.WithSourceGeneratorExecutionVersions(updates, cancellationToken); + }, + static (_, _) => (WorkspaceChangeKind.SolutionChanged, projectId: null, documentId: null), + onBeforeUpdate: null, + onAfterUpdate: null, + cancellationToken).ConfigureAwait(false); + + return; + + static SourceGeneratorExecutionVersionMap GetUpdatedSourceGeneratorVersions( + Solution solution, ImmutableSegmentedList<(ProjectId? projectId, bool forceRegeneration)> projectIds) + { + // For all the projects explicitly requested, update their source generator version. Do this for all + // projects that transitively depend on that project, so that their generators will run as well when next + // asked. + var dependencyGraph = solution.GetProjectDependencyGraph(); + var result = ImmutableSegmentedDictionary.CreateBuilder(); + + // Determine if we want a major solution change, forcing regeneration of all projects. + var solutionMajor = projectIds.Any(t => t.projectId is null && t.forceRegeneration); + + // If it's not a major solution change, then go update the versions for all projects requested. + if (!solutionMajor) + { + // Do a pass where we update minor versions if requested. + PopulateSourceGeneratorExecutionVersions(major: false); + + // Then update major versions. We do this after the minor-version pass so that major version updates + // overwrite minor-version updates. + PopulateSourceGeneratorExecutionVersions(major: true); + } + + // Now, if we've been asked to do an entire solution update, get any projects we didn't already mark, and + // update their execution version as well. + if (projectIds.Any(t => t.projectId is null)) + { + foreach (var projectId in solution.ProjectIds) + { + if (!result.ContainsKey(projectId)) + { + result.Add( + projectId, + Increment(solution.GetSourceGeneratorExecutionVersion(projectId), solutionMajor)); + } + } + } + + return new(result.ToImmutable()); + + void PopulateSourceGeneratorExecutionVersions(bool major) + { + foreach (var (projectId, forceRegeneration) in projectIds) + { + if (projectId is null) + continue; + + if (forceRegeneration != major) + continue; + + // We may have been asked to rerun generators for a project that is no longer around. So make sure + // we still have this project. + var requestedProject = solution.GetProject(projectId); + if (requestedProject != null) + { + result[projectId] = Increment(solution.GetSourceGeneratorExecutionVersion(projectId), major); + + foreach (var transitiveProjectId in dependencyGraph.GetProjectsThatTransitivelyDependOnThisProject(projectId)) + result[transitiveProjectId] = Increment(solution.GetSourceGeneratorExecutionVersion(transitiveProjectId), major); + } + } + } + + static SourceGeneratorExecutionVersion Increment(SourceGeneratorExecutionVersion version, bool major) + => major ? version.IncrementMajorVersion() : version.IncrementMinorVersion(); + } + } +} diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf index 01a6177cdbf0a..93569dd7abbc2 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + Příkaz CodeAction {0} nevytvořil změněné řešení. "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf index 1f0421533b60b..4dbc4bbf1268c 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + Durch CodeAction "{0}" wurde keine geänderte Lösung erstellt. "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf index 2210f10672a40..d79f326dae933 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + El tipo CodeAction "{0}" no generó una solución modificada "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf index bb45ae4e0a334..4437971ba7614 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + Le CodeAction '{0}' n'a pas produit de solution contenant des changements "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf index 2859f6de346ec..77eab2b223826 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + L'elemento CodeAction '{0}' non ha generato una soluzione modificata "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf index 71c364b2a340f..7f16149e410ce 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + CodeAction '{0}' は変更されたソリューションを生成しませんでした "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf index 610a8d087a4a6..ab6a3d0da4853 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + CodeAction '{0}'이(가) 변경된 솔루션을 생성하지 않았습니다. "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf index 978ac0bbe0bc0..5d6df9750c27b 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + Element CodeAction „{0}” nie utworzył zmienionego rozwiązania "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf index 0c2903fd16493..d61231dbb8017 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + A CodeAction '{0}' não produziu uma solução alterada "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf index 0795ab056bb85..a59fe061c71d0 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + Действие кода "{0}" не сформировало измененное решение. "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf index 90339083761b3..a27884ea21b81 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + '{0}' CodeAction, değiştirilmiş çözüm üretmedi "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf index f08d483010a6a..282030d47ddc5 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + CodeAction "{0}" 未生成更改的解决方案 "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf index b9a09d50a4d87..fa3e4431600d7 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf @@ -39,7 +39,7 @@ CodeAction '{0}' did not produce a changed solution - CodeAction '{0}' did not produce a changed solution + CodeAction '{0}' 未產生變更的解決方案 "CodeAction" is a specific type, and {0} represents the title shown by the action. diff --git a/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs b/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs index 4c40c9a8b6565..f948925ecc2f0 100644 --- a/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs +++ b/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs @@ -107,7 +107,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) if (_nested) { - fixes = [CodeAction.Create("Container", fixes.ToImmutableArray(), isInlinable: false)]; + fixes = [CodeAction.Create("Container", [.. fixes], isInlinable: false)]; } foreach (var fix in fixes) diff --git a/src/Workspaces/CoreTest/ChecksumTests.cs b/src/Workspaces/CoreTest/ChecksumTests.cs index e56f177d1092f..fe1abf9386512 100644 --- a/src/Workspaces/CoreTest/ChecksumTests.cs +++ b/src/Workspaces/CoreTest/ChecksumTests.cs @@ -49,6 +49,30 @@ public void ValidateChecksumFromSpanSameAsChecksumFromBytes2() Assert.NotEqual(checksum3, checksumA); } + [Fact] + public void ValidateChecksumFromSpanSameAsChecksumFromBytes3() + { + var checksum1 = Checksum.Create("Goo"); + var checksum2 = Checksum.Create("Bar"); + var checksum3 = Checksum.Create("Baz"); + var checksum4 = Checksum.Create("Quux"); + + var checksumA = Checksum.Create(checksum1, checksum2, checksum3, checksum4); + + // Running this test on multiple target frameworks with the same expectation ensures the results match + Assert.Equal(Checksum.FromBase64String("vva1KeNW7vz7PNnIyM3K6g=="), checksumA); + + Assert.NotEqual(checksum1, checksum2); + Assert.NotEqual(checksum2, checksum3); + Assert.NotEqual(checksum3, checksum4); + Assert.NotEqual(checksum4, checksum1); + + Assert.NotEqual(checksum1, checksumA); + Assert.NotEqual(checksum2, checksumA); + Assert.NotEqual(checksum3, checksumA); + Assert.NotEqual(checksum4, checksumA); + } + [Fact] public void ValidateChecksumFromSpanSameAsChecksumFromBytes10() { diff --git a/src/Workspaces/CoreTest/CodeCleanup/CodeCleanupTests.cs b/src/Workspaces/CoreTest/CodeCleanup/CodeCleanupTests.cs index 743a62fa403c2..a890dd0db567c 100644 --- a/src/Workspaces/CoreTest/CodeCleanup/CodeCleanupTests.cs +++ b/src/Workspaces/CoreTest/CodeCleanup/CodeCleanupTests.cs @@ -180,7 +180,7 @@ public void EntireRangeWithTransformation_RemoveClass() var root = await document.GetSyntaxRootAsync(cancellationToken); root = root.RemoveCSharpMember(0); - expectedResult = SpecializedCollections.SingletonEnumerable(root.FullSpan); + expectedResult = [root.FullSpan]; return document.WithSyntaxRoot(root); } @@ -202,7 +202,7 @@ public void EntireRangeWithTransformation_AddMember() var classWithMember = @class.AddCSharpMember(CreateCSharpMethod(), 0); root = root.ReplaceNode(@class, classWithMember); - expectedResult = SpecializedCollections.SingletonEnumerable(root.FullSpan); + expectedResult = [root.FullSpan]; return document.WithSyntaxRoot(root); } @@ -224,7 +224,7 @@ public void RangeWithTransformation_AddMember() var classWithMember = @class.AddCSharpMember(CreateCSharpMethod(), 0); root = root.ReplaceNode(@class, classWithMember); - expectedResult = SpecializedCollections.SingletonEnumerable(root.GetMember(0).GetMember(0).GetCodeCleanupSpan()); + expectedResult = [root.GetMember(0).GetMember(0).GetCodeCleanupSpan()]; return document.WithSyntaxRoot(root); } @@ -246,7 +246,7 @@ public void RangeWithTransformation_RemoveMember() var classWithMember = @class.RemoveCSharpMember(0); root = root.ReplaceNode(@class, classWithMember); - expectedResult = SpecializedCollections.SingletonEnumerable(root.GetMember(0).GetMember(0).GetCodeCleanupSpan()); + expectedResult = [root.GetMember(0).GetMember(0).GetCodeCleanupSpan()]; return document.WithSyntaxRoot(root); } @@ -361,7 +361,7 @@ public void RangeWithTransformation_OutsideOfRange() root = root.ReplaceToken(previousToken, CSharp.SyntaxFactory.Identifier(previousToken.LeadingTrivia, previousToken.ValueText, previousToken.TrailingTrivia)); root = root.ReplaceToken(nextToken, CSharp.SyntaxFactory.Token(nextToken.LeadingTrivia, CSharp.CSharpExtensions.Kind(nextToken), nextToken.TrailingTrivia)); - expectedResult = SpecializedCollections.EmptyEnumerable(); + expectedResult = []; return document.WithSyntaxRoot(root); } diff --git a/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs b/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs index c1a68021dae2b..4c80e4cd799c2 100644 --- a/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs +++ b/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs @@ -23,7 +23,7 @@ internal static EditorConfigFile CreateParseResults(string e list.Add(parseResult); } - return new EditorConfigFile(editorconfigFilePath, list.ToImmutableArray()); + return new EditorConfigFile(editorconfigFilePath, [.. list]); } [Fact] diff --git a/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs b/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs index 8f28f33ba1510..b5d5d44d9e1dd 100644 --- a/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs +++ b/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs @@ -62,7 +62,7 @@ private static void Verify(SolutionKind workspaceKind, IEnumerable decl private static void VerifyResults(IEnumerable declarations, string[] expectedResults) { declarations = declarations.OrderBy(d => d.ToString()); - expectedResults = expectedResults.OrderBy(r => r).ToArray(); + expectedResults = [.. expectedResults.OrderBy(r => r)]; for (var i = 0; i < expectedResults.Length; i++) { diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs deleted file mode 100644 index 21147f43736bd..0000000000000 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.UnitTests.Persistence -{ - [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestTemporaryStorageServiceFactory() - { - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => TrivialTemporaryStorageService.Instance; - } -} diff --git a/src/Workspaces/CoreTest/ObjectSerializationTests.cs b/src/Workspaces/CoreTest/ObjectSerializationTests.cs index cb9a3212a377c..1afaa16289fda 100644 --- a/src/Workspaces/CoreTest/ObjectSerializationTests.cs +++ b/src/Workspaces/CoreTest/ObjectSerializationTests.cs @@ -668,6 +668,78 @@ public void Encodings(Encoding original) EncodingTestHelpers.AssertEncodingsEqual(original, deserialized); } + [Fact] + public void TestMultipleAssetWritingAndReader() + { + using var stream = new MemoryStream(); + + const string GooString = "Goo"; + const string BarString = "Bar"; + var largeString = new string('a', 1024); + + // Write out some initial bytes, to demonstrate the reader not throwing, even if we don't have the right + // validation bytes at the start. + stream.WriteByte(1); + stream.WriteByte(2); + + using (var writer = new ObjectWriter(stream, leaveOpen: true, writeValidationBytes: false)) + { + writer.WriteValidationBytes(); + writer.WriteString(GooString); + writer.WriteString("Bar"); + writer.WriteString(largeString); + + // Random data, not going through the writer. + stream.WriteByte(3); + stream.WriteByte(4); + + // We should be able to write out a new object, using strings we've already seen. + writer.WriteValidationBytes(); + writer.WriteString(largeString); + writer.WriteString("Bar"); + writer.WriteString(GooString); + } + + stream.Position = 0; + + using var reader = ObjectReader.GetReader(stream, leaveOpen: true, checkValidationBytes: false); + + Assert.Equal(1, reader.ReadByte()); + Assert.Equal(2, reader.ReadByte()); + + reader.CheckValidationBytes(); + + var string1 = reader.ReadString(); + var string2 = reader.ReadString(); + var string3 = reader.ReadString(); + Assert.Equal(GooString, string1); + Assert.Equal(BarString, string2); + Assert.Equal(largeString, string3); + Assert.NotSame(GooString, string1); + Assert.NotSame(BarString, string2); + Assert.NotSame(largeString, string3); + + Assert.Equal(3, stream.ReadByte()); + Assert.Equal(4, stream.ReadByte()); + + reader.CheckValidationBytes(); + var string4 = reader.ReadString(); + var string5 = reader.ReadString(); + var string6 = reader.ReadString(); + Assert.Equal(largeString, string4); + Assert.Equal(BarString, string5); + Assert.Equal(GooString, string6); + Assert.NotSame(largeString, string4); + Assert.NotSame(BarString, string5); + Assert.NotSame(GooString, string6); + + // These should be references to the same strings in the format string and should return the values already + // returned. + Assert.Same(string1, string6); + Assert.Same(string2, string5); + Assert.Same(string3, string4); + } + // keep these around for analyzing perf issues #if false [Fact] diff --git a/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs b/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs index aef8ea9dcbb37..3242dead125c5 100644 --- a/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs +++ b/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs @@ -53,13 +53,13 @@ public class ServiceDescriptorTests { public static IEnumerable AllServiceDescriptors => ServiceDescriptors.Instance.GetTestAccessor().Descriptors - .Select(descriptor => new object[] { descriptor.Key, descriptor.Value.descriptor64, descriptor.Value.descriptor64ServerGC, descriptor.Value.descriptorCoreClr64, descriptor.Value.descriptorCoreClr64ServerGC }); + .Select(descriptor => new object[] { descriptor.Key, descriptor.Value.descriptorCoreClr64, descriptor.Value.descriptorCoreClr64ServerGC }); private static Dictionary GetAllParameterTypesOfRemoteApis() { var interfaces = new List(); - foreach (var (serviceType, (descriptor, _, _, _)) in ServiceDescriptors.Instance.GetTestAccessor().Descriptors) + foreach (var (serviceType, (descriptor, _)) in ServiceDescriptors.Instance.GetTestAccessor().Descriptors) { interfaces.Add(serviceType); if (descriptor.ClientInterface != null) @@ -363,20 +363,16 @@ public void TypesUsedInRemoteApisMustBeMessagePackSerializable() [MemberData(nameof(AllServiceDescriptors))] internal void GetFeatureDisplayName( Type serviceInterface, - ServiceDescriptor descriptor64, - ServiceDescriptor descriptor64ServerGC, ServiceDescriptor descriptorCoreClr64, ServiceDescriptor descriptorCoreClr64ServerGC) { Assert.NotNull(serviceInterface); - var expectedName = descriptor64.GetFeatureDisplayName(); + var expectedName = descriptorCoreClr64.GetFeatureDisplayName(); // The service name couldn't be found. It may need to be added to RemoteWorkspacesResources.resx as FeatureName_{name} Assert.False(string.IsNullOrEmpty(expectedName), $"Service name for '{serviceInterface.GetType()}' not available."); - Assert.Equal(expectedName, descriptor64ServerGC.GetFeatureDisplayName()); - Assert.Equal(expectedName, descriptorCoreClr64.GetFeatureDisplayName()); Assert.Equal(expectedName, descriptorCoreClr64ServerGC.GetFeatureDisplayName()); } @@ -387,7 +383,7 @@ public void CallbackDispatchers() var callbackDispatchers = ((IMefHostExportProvider)hostServices).GetExports(); var descriptorsWithCallbackServiceTypes = ServiceDescriptors.Instance.GetTestAccessor().Descriptors - .Where(d => d.Value.descriptor64.ClientInterface != null).Select(d => d.Key); + .Where(d => d.Value.descriptorCoreClr64.ClientInterface != null).Select(d => d.Key); var callbackDispatcherServiceTypes = callbackDispatchers.Select(d => d.Metadata.ServiceInterface); AssertEx.SetEqual(descriptorsWithCallbackServiceTypes, callbackDispatcherServiceTypes); diff --git a/src/Workspaces/CoreTest/SolutionTests/ProjectDependencyGraphTests.cs b/src/Workspaces/CoreTest/SolutionTests/ProjectDependencyGraphTests.cs index 4de4ff48d5342..8d1c485e5fd0b 100644 --- a/src/Workspaces/CoreTest/SolutionTests/ProjectDependencyGraphTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/ProjectDependencyGraphTests.cs @@ -214,8 +214,7 @@ public void TestTransitiveReferencesIncrementalUpdateWithProjectThatHasUnknownRe // so we shouldn't have any information for A, B, or C and have to deal with that. var solution = CreateSolutionFromReferenceMap("A B C:D D"); - solution = solution.WithProjectReferences(solution.GetProjectsByName("C").Single().Id, - SpecializedCollections.EmptyEnumerable()); + solution = solution.WithProjectReferences(solution.GetProjectsByName("C").Single().Id, []); VerifyTransitiveReferences(solution, "A", []); @@ -394,8 +393,7 @@ public void TestReverseTransitiveReferencesForUnrelatedProjectAfterWithProjectRe VerifyReverseTransitiveReferences(solution, "C", []); VerifyReverseTransitiveReferences(solution, "D", ["C"]); - solution = solution.WithProjectReferences(solution.GetProjectsByName("C").Single().Id, - SpecializedCollections.EmptyEnumerable()); + solution = solution.WithProjectReferences(solution.GetProjectsByName("C").Single().Id, []); VerifyReverseTransitiveReferences(solution, "B", ["A"]); } diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs index 0cdc9229c893f..fa04089a158a9 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests @@ -17,17 +16,8 @@ internal static class SolutionTestHelpers public static Workspace CreateWorkspace(Type[]? additionalParts = null, TestHost testHost = TestHost.InProcess) => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).WithTestHostParts(testHost).GetHostServices()); - public static Workspace CreateWorkspaceWithNormalText() - => CreateWorkspace( - [ - typeof(TestTemporaryStorageServiceFactory) - ]); - - public static Workspace CreateWorkspaceWithRecoverableText() - => CreateWorkspace(); - public static Workspace CreateWorkspaceWithPartialSemantics(TestHost testHost = TestHost.InProcess) - => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics([typeof(TestTemporaryStorageServiceFactory)], testHost); + => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics(testHost: testHost); #nullable disable diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index bb890884334b4..ac504d8783806 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -16,6 +16,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using ICSharpCode.Decompiler.Solution; using Microsoft.Build.Evaluation; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -25,6 +26,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; @@ -2214,7 +2216,8 @@ private static async Task ValidateSolutionAndCompilationsAsync(Solution solution { if (solution.ContainsProject(referenced.ProjectId)) { - var referencedMetadata = await solution.CompilationState.GetMetadataReferenceAsync(referenced, solution.GetProjectState(project.Id), CancellationToken.None); + var referencedMetadata = await solution.CompilationState.GetMetadataReferenceAsync( + referenced, solution.GetProjectState(project.Id), includeCrossLanguage: true, CancellationToken.None); Assert.NotNull(referencedMetadata); if (referencedMetadata is CompilationReference compilationReference) { @@ -2636,11 +2639,8 @@ public void TestUpdatingFilePathUpdatesSyntaxTree() } } -#if NETCOREAPP - [SupportedOSPlatform("windows")] -#endif [MethodImpl(MethodImplOptions.NoInlining)] - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542736")] + [ConditionalFact(typeof(WindowsOnly)), WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542736")] public void TestDocumentChangedOnDiskIsNotObserved() { var text1 = "public class A {}"; @@ -2649,7 +2649,7 @@ public void TestDocumentChangedOnDiskIsNotObserved() var file = Temp.CreateFile().WriteAllText(text1, Encoding.UTF8); // create a solution that evicts from the cache immediately. - using var workspace = CreateWorkspaceWithRecoverableText(); + using var workspace = CreateWorkspace(); var sol = workspace.CurrentSolution; var pid = ProjectId.CreateNewId(); @@ -2666,17 +2666,7 @@ public void TestDocumentChangedOnDiskIsNotObserved() Assert.Equal(text2, textOnDisk); // stop observing it and let GC reclaim it - if (PlatformInformation.IsWindows || PlatformInformation.IsRunningOnMono) - { - Assert.IsType(workspace.Services.GetService()); - observedText.AssertReleased(); - } - else - { - // If this assertion fails, it means a new target supports the true temporary storage service, and the - // condition above should be updated to ensure 'AssertReleased' is called for this target. - Assert.IsType(workspace.Services.GetService()); - } + observedText.AssertReleased(); // if we ask for the same text again we should get the original content var observedText2 = sol.GetDocument(did).GetTextAsync().Result; @@ -3459,6 +3449,34 @@ await documentToFreeze.Project.GetSemanticVersionAsync(), await frozenDocument.Project.GetSemanticVersionAsync()); } + [Fact] + public async Task TestFreezingTwiceGivesSameDocument() + { + using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics(); + var project = workspace.CurrentSolution.AddProject("CSharpProject", "CSharpProject", LanguageNames.CSharp); + project = project.AddDocument("Extra.cs", SourceText.From("class Extra { }")).Project; + + var documentToFreeze = project.AddDocument("DocumentToFreeze.cs", SourceText.From("")); + var frozenDocument = documentToFreeze.WithFrozenPartialSemantics(CancellationToken.None); + + // Because we had no compilation produced yet, we expect that only the DocumentToFreeze is in the compilation + Assert.NotSame(frozenDocument, documentToFreeze); + var tree = Assert.Single((await frozenDocument.Project.GetCompilationAsync()).SyntaxTrees); + Assert.Equal("DocumentToFreeze.cs", tree.FilePath); + + // Versions should be different + Assert.NotEqual( + await documentToFreeze.Project.GetDependentSemanticVersionAsync(), + await frozenDocument.Project.GetDependentSemanticVersionAsync()); + + Assert.NotEqual( + await documentToFreeze.Project.GetSemanticVersionAsync(), + await frozenDocument.Project.GetSemanticVersionAsync()); + + var frozenDocument2 = frozenDocument.WithFrozenPartialSemantics(CancellationToken.None); + Assert.Same(frozenDocument, frozenDocument2); + } + [Fact] public async Task TestFrozenPartialProjectHasDifferentSemanticVersions_ChangedDoc1() { @@ -3905,7 +3923,7 @@ public void TestUpdateDocumentsOrder() var pid = ProjectId.CreateNewId(); VersionStamp GetVersion() => solution.GetProject(pid).Version; - ImmutableArray GetDocumentIds() => solution.GetProject(pid).DocumentIds.ToImmutableArray(); + ImmutableArray GetDocumentIds() => [.. solution.GetProject(pid).DocumentIds]; ImmutableArray GetSyntaxTrees() { return solution.GetProject(pid).GetCompilationAsync().Result.SyntaxTrees.ToImmutableArray(); @@ -4275,6 +4293,303 @@ public void AddingAndRemovingProjectsUpdatesFilePathMap() Assert.Empty(solution.GetDocumentIdsWithFilePath(editorConfigFilePath)); } + [Fact] + public async Task AddMultipleProjects1() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + using var _ = ArrayBuilder.GetInstance(out var projects); + projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp)); + projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp)); + + solution = solution.AddProjects(projects); + + Assert.Equal(2, solution.ProjectIds.Count); + + var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync(); + var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync(); + } + + [Fact] + public async Task AddMultipleProjects2() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + using var _ = ArrayBuilder.GetInstance(out var projects); + + // Add a project that has a reference to the project that follows. + projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp, projectReferences: [new ProjectReference(projectId2)])); + projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp)); + + solution = solution.AddProjects(projects); + + Assert.Equal(2, solution.ProjectIds.Count); + + Assert.True(solution.GetProject(projectId1).ProjectReferences.Contains(p => p.ProjectId == projectId2)); + + var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync(); + var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync(); + + Assert.True(compilation1.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation2)); + } + + [Fact] + public async Task AddMultipleProjects3() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + using var _ = ArrayBuilder.GetInstance(out var projects); + + // Add a project that has a reference to the project that precedes it. + projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp)); + projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp, projectReferences: [new ProjectReference(projectId1)])); + + solution = solution.AddProjects(projects); + + Assert.Equal(2, solution.ProjectIds.Count); + + Assert.True(solution.GetProject(projectId2).ProjectReferences.Contains(p => p.ProjectId == projectId1)); + + var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync(); + var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync(); + + Assert.True(compilation2.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation1)); + } + + [Fact] + public async Task AddMultipleProjects4() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + var projectId3 = ProjectId.CreateNewId(); + + solution = solution.AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp)); + var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync(); + + using var _ = ArrayBuilder.GetInstance(out var projects); + + projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp)); + projects.Add(ProjectInfo.Create(projectId3, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp)); + + solution = solution.AddProjects(projects); + + Assert.Equal(3, solution.ProjectIds.Count); + + var compilation1New = await solution.GetProject(projectId1).GetCompilationAsync(); + + // These compilations should be the same as adding the later projects should have no impact on the first project. + Assert.Same(compilation1, compilation1New); + } + + [Fact] + public async Task AddMultipleProjects5() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + var projectId3 = ProjectId.CreateNewId(); + + // Reference a project that will be added later. + solution = solution.AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp, projectReferences: [new ProjectReference(projectId2)])); + var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync(); + + using var _ = ArrayBuilder.GetInstance(out var projects); + + // Add a project that has a reference to the project that precedes it. + projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp)); + projects.Add(ProjectInfo.Create(projectId3, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp)); + + solution = solution.AddProjects(projects); + + Assert.Equal(3, solution.ProjectIds.Count); + + var compilation1New = await solution.GetProject(projectId1).GetCompilationAsync(); + + // These compilations should not be the same as adding the later projects should fork the first project. + Assert.NotSame(compilation1, compilation1New); + } + + [Fact] + public async Task AddMultipleProjects6() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + var projectId3 = ProjectId.CreateNewId(); + + // Reference both projects that will be added later. + solution = solution.AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp, + projectReferences: [new ProjectReference(projectId2), new ProjectReference(projectId3)])); + var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync(); + + using var _ = ArrayBuilder.GetInstance(out var projects); + + // Add a project that has a reference to the project that precedes it. + projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp)); + projects.Add(ProjectInfo.Create(projectId3, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp)); + + solution = solution.AddProjects(projects); + + Assert.Equal(3, solution.ProjectIds.Count); + + var compilation1New = await solution.GetProject(projectId1).GetCompilationAsync(); + + // These compilations should not be the same as adding the later projects should fork the first project. + Assert.NotSame(compilation1, compilation1New); + + var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync(); + var compilation3 = await solution.GetProject(projectId2).GetCompilationAsync(); + + Assert.True(compilation1New.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation2)); + Assert.True(compilation1New.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation3)); + } + + [Fact] + public void AddMultipleProjects_ThrowOnDuplicateId() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var projectId1 = ProjectId.CreateNewId(); + + using var _ = ArrayBuilder.GetInstance(out var projects); + + // Add a project that has a reference to the project that precedes it. + projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp)); + projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp)); + + Assert.Throws(() => solution.AddProjects(projects)); + } + + [Fact] + public async Task RemoveMultipleProjects1() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + var projectId3 = ProjectId.CreateNewId(); + + using var _ = ArrayBuilder.GetInstance(out var projects); + + // Add a project that has a reference to the later projects + projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp, + projectReferences: [new ProjectReference(projectId2), new ProjectReference(projectId3)])); + projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp)); + projects.Add(ProjectInfo.Create(projectId3, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp)); + + solution = solution.AddProjects(projects); + + Assert.Equal(3, solution.ProjectIds.Count); + + var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync(); + var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync(); + var compilation3 = await solution.GetProject(projectId2).GetCompilationAsync(); + + Assert.True(compilation1.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation2)); + Assert.True(compilation1.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation3)); + + using var _2 = ArrayBuilder.GetInstance(out var projectsToRemove); + projectsToRemove.Add(projectId2); + projectsToRemove.Add(projectId3); + + solution = solution.RemoveProjects(projectsToRemove); + + Assert.Equal(1, solution.ProjectIds.Count); + + Assert.Equal(projectId1, solution.ProjectIds.Single()); + var compilation1New = await solution.Projects.Single().GetCompilationAsync(); + + Assert.NotSame(compilation1, compilation1New); + Assert.True(compilation1New.References.All(r => r is not CompilationReference)); + } + + [Fact] + public async Task RemoveMultipleProjects2() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + var projectId3 = ProjectId.CreateNewId(); + + using var _ = ArrayBuilder.GetInstance(out var projects); + + // All projects are independent + projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp)); + projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp)); + projects.Add(ProjectInfo.Create(projectId3, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp)); + + solution = solution.AddProjects(projects); + + Assert.Equal(3, solution.ProjectIds.Count); + + var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync(); + var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync(); + var compilation3 = await solution.GetProject(projectId2).GetCompilationAsync(); + + Assert.False(compilation1.References.Any(r => r is CompilationReference)); + + using var _2 = ArrayBuilder.GetInstance(out var projectsToRemove); + projectsToRemove.Add(projectId2); + projectsToRemove.Add(projectId3); + + solution = solution.RemoveProjects(projectsToRemove); + + Assert.Equal(1, solution.ProjectIds.Count); + + Assert.Equal(projectId1, solution.ProjectIds.Single()); + var compilation1New = await solution.Projects.Single().GetCompilationAsync(); + + // Removing project2 and project3 should not change project1 here. + Assert.Same(compilation1, compilation1New); + } + + [Fact] + public void RemoveMultipleProjects_ThrowOnDuplicateId() + { + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution; + + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + using var _ = ArrayBuilder.GetInstance(out var projects); + + // Add a project that has a reference to the project that precedes it. + projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp)); + projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp)); + + solution = solution.AddProjects(projects); + + using var _2 = ArrayBuilder.GetInstance(out var projectsToRemove); + projectsToRemove.Add(projectId1); + projectsToRemove.Add(projectId1); + + Assert.Throws(() => solution.RemoveProjects(projectsToRemove)); + } + private static void GetMultipleProjects( out Project csBrokenProject, out Project vbNormalProject, @@ -4518,6 +4833,23 @@ public void GetRelatedDocumentsDoesNotReturnOtherTypesOfDocuments() Assert.Single(solution.GetRelatedDocumentIds(regularDocumentId)); } + [Theory, CombinatorialData] + public void GetRelatedDocumentsCaseInsensitive( + [CombinatorialValues("file", "File", "FILE", "FiLe")] string prefix, + [CombinatorialValues("cs", "Cs", "cS", "CS")] string extension) + { + using var workspace = CreateWorkspace(); + + var solution = workspace.CurrentSolution + .AddProject("TestProject1", "TestProject1", LanguageNames.CSharp) + .AddDocument("File.cs", "", filePath: "File.cs").Project.Solution + .AddProject("TestProject2", "TestProject2", LanguageNames.CSharp) + .AddDocument("file.cs", "", filePath: "file.cs").Project.Solution; + + // GetDocumentIdsWithFilePath should return two, since it'll count all types of documents + Assert.Equal(2, solution.GetDocumentIdsWithFilePath($"{prefix}.{extension}").Length); + } + [Fact] public async Task TestFrozenPartialSolution1() { @@ -4735,5 +5067,62 @@ public async Task TestFrozenPartialSolutionOtherLanguage() var frozenCompilation = await frozenProject.GetCompilationAsync(); Assert.Null(frozenCompilation); } + + [Theory] + [InlineData(1000)] + [InlineData(2000)] + [InlineData(4000)] + [InlineData(8000)] + public async Task TestLargeLinkedFileChain(int intermediatePullCount) + { + using var workspace = CreateWorkspace(); + + var project1 = workspace.CurrentSolution + .AddProject($"Project1", $"Project1", LanguageNames.CSharp) + .WithParseOptions(CSharpParseOptions.Default.WithPreprocessorSymbols("DEBUG")) + .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; + var documentId1 = project1.DocumentIds.Single(); + + // make another project, give a separate set of pp directives, so that we do *not* try to use the sibling + // root (from project1), but instead incrementally parse using the *contents* of the file in project1 again + // our actual tree. This used to stack overflow since we'd create a long chain of incremental parsing steps + // for each edit made to the sibling file. + var project2 = project1.Solution + .AddProject($"Project2", $"Project2", LanguageNames.CSharp) + .WithParseOptions(CSharpParseOptions.Default.WithPreprocessorSymbols("RELEASE")) + .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; + var documentId2 = project2.DocumentIds.Single(); + + workspace.SetCurrentSolution( + _ => project2.Solution, + (_, _) => (WorkspaceChangeKind.SolutionAdded, null, null)); + + for (var i = 1; i <= 8000; i++) + { + var lastContents = $"#if true //{new string('.', i)}//"; + workspace.SetCurrentSolution( + old => old.WithDocumentText(documentId1, SourceText.From(lastContents)), + (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1)); + + // Ensure that the first document is fine, and we're not stack overflowing on simply getting the tree + // from it. Do this on a disparate cadence from our pulls of the second document to ensure we are + // testing the case where we haven't necessarily immediately pulled on hte first doc before pulling on + // the second. + if (i % 33 == 0) + { + var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1); + await document1.GetSyntaxRootAsync(); + } + + if (i % intermediatePullCount == 0) + { + var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); + + // Getting the second document should both be fine, and have contents equivalent to what is in the first document. + var root = await document2.GetSyntaxRootAsync(); + Assert.Equal(lastContents, root.ToFullString()); + } + } + } } } diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs index 1a5d986601cff..f8daa66a477e5 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Immutable; -using System.Drawing.Text; using System.Linq; using System.Text; using System.Threading; @@ -25,7 +24,7 @@ namespace Microsoft.CodeAnalysis.UnitTests { [UseExportProvider] - public class SolutionWithSourceGeneratorTests : TestBase + public sealed class SolutionWithSourceGeneratorTests : TestBase { [Theory, CombinatorialData] public async Task SourceGeneratorBasedOnAdditionalFileGeneratesSyntaxTrees( @@ -87,7 +86,7 @@ public async Task WithReferencesMethodCorrectlyUpdatesWithEqualReferences(TestHo Assert.Single((await project.GetRequiredCompilationAsync(CancellationToken.None)).SyntaxTrees); // Now remove and confirm that we don't have any files - project = project.WithAnalyzerReferences(SpecializedCollections.EmptyEnumerable()); + project = project.WithAnalyzerReferences([]); Assert.Empty((await project.GetRequiredCompilationAsync(CancellationToken.None)).SyntaxTrees); } @@ -834,9 +833,11 @@ public async Task FreezingSourceGeneratedDocumentsWorks(TestHost testHost) static async Task AssertFrozen(Project project, SourceGeneratedDocumentIdentity identity) { - var frozenWithSingleDocument = project.Solution.WithFrozenSourceGeneratedDocument(identity, SourceText.From("// Frozen Document")); + var frozenWithSingleDocument = project.Solution.WithFrozenSourceGeneratedDocument( + identity, DateTime.Now, SourceText.From("// Frozen Document")); Assert.Equal("// Frozen Document", (await frozenWithSingleDocument.GetTextAsync()).ToString()); - var frozenTree = Assert.Single((await frozenWithSingleDocument.Project.GetRequiredCompilationAsync(CancellationToken.None)).SyntaxTrees); + var syntaxTrees = (await frozenWithSingleDocument.Project.GetRequiredCompilationAsync(CancellationToken.None)).SyntaxTrees; + var frozenTree = Assert.Single(syntaxTrees); Assert.Equal("// Frozen Document", frozenTree.ToString()); } } @@ -860,7 +861,7 @@ public async Task FreezingSourceGeneratedDocumentsInTwoProjectsWorks(TestHost te // And now freeze both of them at once var solutionWithFrozenDocuments = solution.WithFrozenSourceGeneratedDocuments( - [(sourceGeneratedDocument1.Identity, SourceText.From("// Frozen 1")), (sourceGeneratedDocument2.Identity, SourceText.From("// Frozen 2"))]); + [(sourceGeneratedDocument1.Identity, DateTime.Now, SourceText.From("// Frozen 1")), (sourceGeneratedDocument2.Identity, DateTime.Now, SourceText.From("// Frozen 2"))]); Assert.Equal("// Frozen 1", (await (await solutionWithFrozenDocuments.GetRequiredProject(projectId1).GetSourceGeneratedDocumentsAsync()).Single().GetTextAsync()).ToString()); Assert.Equal("// Frozen 2", (await (await solutionWithFrozenDocuments.GetRequiredProject(projectId2).GetSourceGeneratedDocumentsAsync()).Single().GetTextAsync()).ToString()); @@ -879,7 +880,8 @@ public async Task FreezingWithSameContentDoesNotFork(TestHost testHost) var sourceGeneratedDocument = Assert.Single(await project.GetSourceGeneratedDocumentsAsync()); var sourceGeneratedDocumentIdentity = sourceGeneratedDocument.Identity; - var frozenSolution = project.Solution.WithFrozenSourceGeneratedDocument(sourceGeneratedDocumentIdentity, SourceText.From("// Hello, World")); + var frozenSolution = project.Solution.WithFrozenSourceGeneratedDocument( + sourceGeneratedDocumentIdentity, sourceGeneratedDocument.GenerationDateTime, SourceText.From("// Hello, World")); Assert.Same(project.Solution, frozenSolution.Project.Solution); } } diff --git a/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs b/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs index 63807101f0a66..b9e1680f9a148 100644 --- a/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs @@ -31,7 +31,7 @@ public CustomizedCanApplyWorkspace(ApplyChangesKind[] allowedKinds, Func? canApplyCompilationOptions = null) : base(Host.Mef.MefHostServices.DefaultHost, workspaceKind: nameof(CustomizedCanApplyWorkspace)) { - _allowedKinds = allowedKinds.ToImmutableArray(); + _allowedKinds = [.. allowedKinds]; _canApplyParseOptions = canApplyParseOptions; _canApplyCompilationOptions = canApplyCompilationOptions; diff --git a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs index 2ce0a35a4bb70..6614d2e7f9b26 100644 --- a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs +++ b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs @@ -2,34 +2,26 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Linq; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Roslyn.Test.Utilities; using Xunit; +using Microsoft.CodeAnalysis.Shared.Extensions; using CS = Microsoft.CodeAnalysis.CSharp; using VB = Microsoft.CodeAnalysis.VisualBasic; +using System.Threading.Tasks; +using System.Threading; namespace Microsoft.CodeAnalysis.UnitTests { [UseExportProvider] [Trait(Traits.Feature, Traits.Features.Workspace)] - public class SyntaxReferenceTests : TestBase + public sealed class SyntaxReferenceTests : TestBase { - private static Workspace CreateWorkspace(Type[] additionalParts = null) - => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).GetHostServices()); - - private static Workspace CreateWorkspaceWithRecoverableSyntaxTrees() - => CreateWorkspace( - [ - typeof(TestTemporaryStorageServiceFactory) - ]); + private static Workspace CreateWorkspace() + => new AdhocWorkspace(FeaturesTestCompositions.Features.GetHostServices()); private static Solution AddSingleFileCSharpProject(Solution solution, string source) { @@ -52,16 +44,16 @@ private static Solution AddSingleFileVisualBasicProject(Solution solution, strin } [Fact] - public void TestCSharpReferenceToZeroWidthNode() + public async Task TestCSharpReferenceToZeroWidthNode() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileCSharpProject(workspace.CurrentSolution, @" public class C<> { } "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // this is an expected TypeParameterSyntax with a missing identifier token (it is zero-length w/ an error attached to it) var node = tree.GetRoot().DescendantNodes().OfType().Single(); @@ -75,15 +67,15 @@ public class C<> } [Fact] - public void TestVisualBasicReferenceToZeroWidthNode() + public async Task TestVisualBasicReferenceToZeroWidthNode() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileVisualBasicProject(workspace.CurrentSolution, @" Public Class C(Of ) End Class "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // this is an expected TypeParameterSyntax with a missing identifier token (it is zero-length w/ an error attached to it) var node = tree.GetRoot().DescendantNodes().OfType().Single(); @@ -97,9 +89,9 @@ End Class } [Fact] - public void TestCSharpReferenceToNodeInStructuredTrivia() + public async Task TestCSharpReferenceToNodeInStructuredTrivia() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileCSharpProject(workspace.CurrentSolution, @" #if true || true public class C @@ -107,7 +99,7 @@ public class C } #endif "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var node = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -120,9 +112,9 @@ public class C } [Fact] - public void TestVisualBasicReferenceToNodeInStructuredTrivia() + public async Task TestVisualBasicReferenceToNodeInStructuredTrivia() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileVisualBasicProject(workspace.CurrentSolution, @" #If True Or True Then Public Class C @@ -130,7 +122,7 @@ End Class #End If "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var node = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -143,9 +135,9 @@ End Class } [Fact] - public void TestCSharpReferenceToZeroWidthNodeInStructuredTrivia() + public async Task TestCSharpReferenceToZeroWidthNodeInStructuredTrivia() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileCSharpProject(workspace.CurrentSolution, @" #if true || public class C @@ -154,7 +146,7 @@ public class C #endif "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var binary = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -173,7 +165,7 @@ public class C [Fact] public async System.Threading.Tasks.Task TestVisualBasicReferenceToZeroWidthNodeInStructuredTriviaAsync() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileVisualBasicProject(workspace.CurrentSolution, @" #If (True Or ) Then Public Class C @@ -181,7 +173,7 @@ End Class #End If "); - var tree = await solution.Projects.First().Documents.First().GetSyntaxTreeAsync(); + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var binary = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); diff --git a/src/Workspaces/CoreTest/TestCompositionTests.cs b/src/Workspaces/CoreTest/TestCompositionTests.cs index 13237a0dcb699..24535fa007888 100644 --- a/src/Workspaces/CoreTest/TestCompositionTests.cs +++ b/src/Workspaces/CoreTest/TestCompositionTests.cs @@ -2,21 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests { + using static SourceGeneratorTelemetryCollectorWorkspaceServiceTests; + public class TestCompositionTests { [Fact] public void FactoryReuse() { - var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestErrorReportingService), typeof(TestTemporaryStorageServiceFactory)); - var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestTemporaryStorageServiceFactory), typeof(TestErrorReportingService)); + var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestErrorReportingService), typeof(TestSourceGeneratorTelemetryCollectorWorkspaceServiceFactory)); + var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestSourceGeneratorTelemetryCollectorWorkspaceServiceFactory), typeof(TestErrorReportingService)); Assert.Same(composition1.ExportProviderFactory, composition2.ExportProviderFactory); } diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index bba69b5e4b499..053cad8235405 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -18,6 +18,8 @@ namespace Microsoft.CodeAnalysis.UnitTests { + using static TemporaryStorageService; + [UseExportProvider] #if NETCOREAPP [SupportedOSPlatform("windows")] @@ -51,7 +53,6 @@ public void TestTemporaryStorageStream() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var temporaryStorage = service.CreateTemporaryStreamStorage(); using var data = SerializableBytes.CreateWritableStream(); for (var i = 0; i < SharedPools.ByteBufferSize; i++) @@ -59,9 +60,9 @@ public void TestTemporaryStorageStream() data.WriteByte((byte)(i % 2)); } - data.Position = 0; - temporaryStorage.WriteStreamAsync(data).Wait(); - using var result = temporaryStorage.ReadStreamAsync().Result; + var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); + + using var result = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(data.Length, result.Length); for (var i = 0; i < SharedPools.ByteBufferSize; i++) @@ -72,65 +73,15 @@ public void TestTemporaryStorageStream() private static void TestTemporaryStorage(ITemporaryStorageServiceInternal temporaryStorageService, SourceText text) { - // create a temporary storage location - var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); - // write text into it - temporaryStorage.WriteTextAsync(text).Wait(); + var handle = temporaryStorageService.WriteToTemporaryStorage(text, CancellationToken.None); // read text back from it - var text2 = temporaryStorage.ReadTextAsync().Result; + var text2 = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); Assert.Equal(text.Encoding, text2.Encoding); - - temporaryStorage.Dispose(); - } - - [ConditionalFact(typeof(WindowsOnly))] - public void TestTemporaryTextStorageExceptions() - { - using var workspace = new AdhocWorkspace(); - var textFactory = Assert.IsType(workspace.Services.GetService()); - var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryTextStorage(); - - // Nothing has been written yet - Assert.Throws(() => storage.ReadText()); - Assert.Throws(() => storage.ReadTextAsync().Result); - - // write a normal string - var text = SourceText.From(new string(' ', 4096) + "public class A {}"); - storage.WriteTextAsync(text).Wait(); - - // Writing multiple times is not allowed - Assert.Throws(() => storage.WriteText(text)); - Assert.Throws(() => storage.WriteTextAsync(text).Wait()); - } - - [ConditionalFact(typeof(WindowsOnly))] - public void TestTemporaryStreamStorageExceptions() - { - using var workspace = new AdhocWorkspace(); - var textFactory = Assert.IsType(workspace.Services.GetService()); - var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); - - // Nothing has been written yet - Assert.Throws(() => storage.ReadStream(CancellationToken.None)); - Assert.Throws(() => storage.ReadStreamAsync().Result); - - // write a normal stream - var stream = new MemoryStream(); - stream.Write([42], 0, 1); - stream.Position = 0; - storage.WriteStreamAsync(stream).Wait(); - - // Writing multiple times is not allowed - // These should also throw before ever getting to the point where they would look at the null stream arg. - Assert.Throws(() => storage.WriteStream(null!)); - Assert.Throws(() => storage.WriteStreamAsync(null!).Wait()); } [ConditionalFact(typeof(WindowsOnly))] @@ -139,15 +90,15 @@ public void TestZeroLengthStreams() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); // 0 length streams are allowed + TemporaryStorageStreamHandle handle; using (var stream1 = new MemoryStream()) { - storage.WriteStream(stream1); + handle = service.WriteToTemporaryStorage(stream1, CancellationToken.None); } - using (var stream2 = storage.ReadStream(CancellationToken.None)) + using (var stream2 = handle.ReadFromTemporaryStorage(CancellationToken.None)) { Assert.Equal(0, stream2.Length); } @@ -170,19 +121,15 @@ public void TestTemporaryStorageMemoryMappedFileManagement() { for (var j = 1; j < 5; j++) { - using ITemporaryStreamStorageInternal storage1 = service.CreateTemporaryStreamStorage(), - storage2 = service.CreateTemporaryStreamStorage(); - var storage3 = service.CreateTemporaryStreamStorage(); // let the finalizer run for this instance - - storage1.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1)); - storage2.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i)); - storage3.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1)); + var handle1 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1), CancellationToken.None); + var handle2 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i), CancellationToken.None); + var handle3 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); await Task.Yield(); - using Stream s1 = storage1.ReadStream(), - s2 = storage2.ReadStream(), - s3 = storage3.ReadStream(CancellationToken.None); + using var s1 = handle1.ReadFromTemporaryStorage(CancellationToken.None); + using var s2 = handle2.ReadFromTemporaryStorage(CancellationToken.None); + using var s3 = handle3.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(1024 * i - 1, s1.Length); Assert.Equal(1024 * i, s2.Length); Assert.Equal(1024 * i + 1, s3.Length); @@ -214,20 +161,17 @@ public void TestTemporaryStorageScaling() // Create 4GB of memory mapped files var fileCount = (int)((long)4 * 1024 * 1024 * 1024 / data.Length); - var storageHandles = new List(fileCount); + var storageHandles = new List(fileCount); for (var i = 0; i < fileCount; i++) { - var s = service.CreateTemporaryStreamStorage(); - storageHandles.Add(s); - data.Position = 0; - s.WriteStreamAsync(data).Wait(); + var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); + storageHandles.Add(handle); } for (var i = 0; i < 1024 * 5; i++) { - using var s = storageHandles[i].ReadStreamAsync().Result; + using var s = storageHandles[i].ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(1, s.ReadByte()); - storageHandles[i].Dispose(); } } } @@ -238,7 +182,6 @@ public void StreamTest1() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); using var expected = new MemoryStream(); for (var i = 0; i < 10000; i++) @@ -246,11 +189,10 @@ public void StreamTest1() expected.WriteByte((byte)(i % byte.MaxValue)); } - expected.Position = 0; - storage.WriteStream(expected); + var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = storage.ReadStream(CancellationToken.None); + using var stream = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(expected.Length, stream.Length); for (var i = 0; i < expected.Length; i++) @@ -265,7 +207,6 @@ public void StreamTest2() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); using var expected = new MemoryStream(); for (var i = 0; i < 10000; i++) @@ -273,11 +214,10 @@ public void StreamTest2() expected.WriteByte((byte)(i % byte.MaxValue)); } - expected.Position = 0; - storage.WriteStream(expected); + var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = storage.ReadStream(CancellationToken.None); + using var stream = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(expected.Length, stream.Length); var index = 0; @@ -302,7 +242,6 @@ public void StreamTest3() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); using var expected = new MemoryStream(); var random = new Random(Environment.TickCount); @@ -315,11 +254,10 @@ public void StreamTest3() expected.WriteByte(value); } - expected.Position = 0; - storage.WriteStream(expected); + var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = storage.ReadStream(CancellationToken.None); + using var stream = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(expected.Length, stream.Length); for (var i = 0; i < expected.Length; i++) diff --git a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs index 60dd23543517c..e9e24ba94cab1 100644 --- a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs +++ b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs @@ -18,30 +18,27 @@ namespace Microsoft.CodeAnalysis.Remote.Testing; /// internal sealed class SimpleAssetSource(ISerializerService serializerService, IReadOnlyDictionary map) : IAssetSource { - public ValueTask> GetAssetsAsync( - Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, ISerializerService deserializerService, CancellationToken cancellationToken) + public ValueTask GetAssetsAsync( + Checksum solutionChecksum, AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService deserializerService, Action callback, TArg arg, CancellationToken cancellationToken) { - var results = new List(); - foreach (var checksum in checksums.Span) { Contract.ThrowIfFalse(map.TryGetValue(checksum, out var data)); using var stream = new MemoryStream(); - using var context = new SolutionReplicationContext(); - using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) + using (var writer = new ObjectWriter(stream, leaveOpen: true)) { - serializerService.Serialize(data, writer, context, cancellationToken); + serializerService.Serialize(data, writer, cancellationToken); } stream.Position = 0; - using var reader = ObjectReader.GetReader(stream, leaveOpen: true, cancellationToken); + using var reader = ObjectReader.GetReader(stream, leaveOpen: true); var asset = deserializerService.Deserialize(data.GetWellKnownSynchronizationKind(), reader, cancellationToken); Contract.ThrowIfNull(asset); - results.Add(asset); + callback(checksum, (T)asset, arg); } - return ValueTaskFactory.FromResult(results.ToImmutableArray()); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs b/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs index da2266620118d..95f12c9ac6ba3 100644 --- a/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs +++ b/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs @@ -33,9 +33,9 @@ public sealed class TestComposition public CacheKey(ImmutableHashSet assemblies, ImmutableHashSet parts, ImmutableHashSet excludedPartTypes) { - _assemblies = assemblies.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName)).ToImmutableArray(); - _parts = parts.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName)).ToImmutableArray(); - _excludedPartTypes = excludedPartTypes.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName)).ToImmutableArray(); + _assemblies = [.. assemblies.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; + _parts = [.. parts.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; + _excludedPartTypes = [.. excludedPartTypes.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; } public override bool Equals(object? obj) diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index d6438a07d9c5a..8808a28046312 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -59,7 +59,7 @@ public RemoteWorkspace GetRemoteWorkspace() public override RemoteServiceConnection CreateConnection(object? callbackTarget) where T : class { - var descriptor = ServiceDescriptors.Instance.GetServiceDescriptor(typeof(T), RemoteProcessConfiguration.ServerGC | (ServiceDescriptors.IsCurrentProcessRunningOnCoreClr() ? RemoteProcessConfiguration.Core : 0)); + var descriptor = ServiceDescriptors.Instance.GetServiceDescriptor(typeof(T), RemoteProcessConfiguration.ServerGC); var callbackDispatcher = (descriptor.ClientInterface != null) ? _callbackDispatchers.GetDispatcher(typeof(T)) : null; return new BrokeredServiceConnection( @@ -212,6 +212,8 @@ public InProcRemoteServices(SolutionServices workspaceServices, TraceListener? t RegisterRemoteBrokeredService(new RemoteStackTraceExplorerService.Factory()); RegisterRemoteBrokeredService(new RemoteUnitTestingSearchService.Factory()); RegisterRemoteBrokeredService(new RemoteSourceGenerationService.Factory()); + RegisterRemoteBrokeredService(new RemoteSemanticSearchService.Factory()); + RegisterRemoteBrokeredService(new RemoteProcessTelemetryService.Factory()); } public void Dispose() diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs index 7f6cfb699835d..e216af1473e25 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs @@ -17,6 +17,7 @@ namespace Microsoft.CodeAnalysis.Remote.Testing { +#pragma warning disable CA1416 // Validate platform compatibility internal sealed class InProcRemoteHostClientProvider : IRemoteHostClientProvider, IDisposable { [ExportWorkspaceServiceFactory(typeof(IRemoteHostClientProvider), ServiceLayer.Test), Shared, PartNotDiscoverable] @@ -99,4 +100,5 @@ public void Dispose() public Task TryGetRemoteHostClientAsync(CancellationToken cancellationToken) => Task.FromResult(_lazyClient.Value); } +#pragma warning restore CA1416 // Validate platform compatibility } diff --git a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs index 8c42582dd37cc..3ce81ef9f9c1c 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs @@ -10,6 +10,7 @@ using System.Collections.Immutable; using System.Composition; using System.Linq; +using System.Runtime.Versioning; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; @@ -19,8 +20,13 @@ using Roslyn.Utilities; using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; +#pragma warning disable CA1416 // Validate platform compatibility + namespace Microsoft.CodeAnalysis.UnitTests.Remote { +#if NETCOREAPP + [SupportedOSPlatform("windows")] +#endif internal sealed class TestSerializerService : SerializerService { private static readonly ImmutableDictionary s_wellKnownReferenceNames = ImmutableDictionary.Create(ReferenceEqualityComparer.Instance) @@ -41,7 +47,7 @@ public TestSerializerService(ConcurrentDictionary _sharedTestGeneratorReferences = sharedTestGeneratorReferences; } - public override void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public override void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { var wellKnownReferenceName = s_wellKnownReferenceNames.GetValueOrDefault(reference, null); if (wellKnownReferenceName is not null) @@ -52,7 +58,7 @@ public override void WriteMetadataReferenceTo(MetadataReference reference, Objec else { writer.WriteBoolean(false); - base.WriteMetadataReferenceTo(reference, writer, context, cancellationToken); + base.WriteMetadataReferenceTo(reference, writer, cancellationToken); } } diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs index dc823e1820886..9bddc611e2816 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs @@ -67,6 +67,7 @@ public partial class TestWorkspace private const string CommonReferencesNetCoreAppName = "CommonReferencesNetCoreApp"; private const string CommonReferencesNet6Name = "CommonReferencesNet6"; private const string CommonReferencesNet7Name = "CommonReferencesNet7"; + private const string CommonReferencesNet8Name = "CommonReferencesNet8"; private const string CommonReferencesNetStandard20Name = "CommonReferencesNetStandard20"; private const string CommonReferencesMinCorlibName = "CommonReferencesMinCorlib"; private const string ReferencesOnDiskAttributeName = "ReferencesOnDisk"; @@ -295,7 +296,7 @@ private static ParseOptions GetParseOptionsWithFeatures(ParseOptions parseOption var key = split[0]; var value = split.Length == 2 ? split[1] : "true"; - return new KeyValuePair(key, value); + return KeyValuePairUtil.Create(key, value); }); return parseOptions.WithFeatures(features); @@ -901,7 +902,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)netcore30).HasValue && ((bool?)netcore30).Value) { - references = NetCoreApp.References.ToList(); + references = [.. NetCoreApp.References]; } var netstandard20 = element.Attribute(CommonReferencesNetStandard20Name); @@ -909,7 +910,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)netstandard20).HasValue && ((bool?)netstandard20).Value) { - references = TargetFrameworkUtil.NetStandard20References.ToList(); + references = [.. TargetFrameworkUtil.NetStandard20References]; } var net6 = element.Attribute(CommonReferencesNet6Name); @@ -917,7 +918,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)net6).HasValue && ((bool?)net6).Value) { - references = TargetFrameworkUtil.GetReferences(TargetFramework.Net60).ToList(); + references = [.. TargetFrameworkUtil.GetReferences(TargetFramework.Net60)]; } var net7 = element.Attribute(CommonReferencesNet7Name); @@ -925,7 +926,15 @@ private IList CreateCommonReferences(XElement element) ((bool?)net7).HasValue && ((bool?)net7).Value) { - references = TargetFrameworkUtil.GetReferences(TargetFramework.Net70).ToList(); + references = [.. TargetFrameworkUtil.GetReferences(TargetFramework.Net70)]; + } + + var net8 = element.Attribute(CommonReferencesNet8Name); + if (net8 != null && + ((bool?)net8).HasValue && + ((bool?)net8).Value) + { + references = [.. TargetFrameworkUtil.GetReferences(TargetFramework.Net80)]; } var mincorlib = element.Attribute(CommonReferencesMinCorlibName); diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs index 95a0a2d362888..af62f5337cb67 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs @@ -347,6 +347,7 @@ private bool KindSupportsAddRemoveDocument() { WorkspaceKind.MiscellaneousFiles => false, WorkspaceKind.Interactive => false, + WorkspaceKind.SemanticSearch => false, _ => true }; @@ -637,7 +638,7 @@ internal void InitializeDocuments( Documents.Add(submission.Documents.Single()); } - var solution = CreateSolution(projectNameToTestHostProject.Values.ToArray()); + var solution = CreateSolution([.. projectNameToTestHostProject.Values]); AddTestSolution(solution); foreach (var projectElement in workspaceElement.Elements(ProjectElementName)) diff --git a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTestBase.cs b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTestBase.cs index 91fc9b2b4328c..dc8986606a5e3 100644 --- a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTestBase.cs +++ b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTestBase.cs @@ -101,11 +101,11 @@ protected static int GetMethodInsertionPoint(VB.Syntax.ClassBlockSyntax classBlo { if (classBlock.Implements.Count > 0) { - return classBlock.Implements[classBlock.Implements.Count - 1].FullSpan.End; + return classBlock.Implements[^1].FullSpan.End; } else if (classBlock.Inherits.Count > 0) { - return classBlock.Inherits[classBlock.Inherits.Count - 1].FullSpan.End; + return classBlock.Inherits[^1].FullSpan.End; } else { diff --git a/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs b/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs index 732ff37083097..230b95db8ef1b 100644 --- a/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs +++ b/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs @@ -2432,7 +2432,7 @@ public async Task TestProjectReferenceWithExternAlias() } [ConditionalFact(typeof(VisualStudioMSBuildInstalled))] - public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse() + public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse_SolutionRoot() { var files = GetProjectReferenceSolutionFiles(); files = VisitProjectReferences( @@ -2451,6 +2451,29 @@ public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse() } } + [ConditionalFact(typeof(VisualStudioMSBuildInstalled))] + public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse_ProjectRoot() + { + var files = GetProjectReferenceSolutionFiles(); + files = VisitProjectReferences( + files, + r => r.Add(new XElement(XName.Get("ReferenceOutputAssembly", MSBuildNamespace), "false"))); + + CreateFiles(files); + + var referencingProjectPath = GetSolutionFileName(@"CSharpProject\CSharpProject_ProjectReference.csproj"); + var referencedProjectPath = GetSolutionFileName(@"CSharpProject\CSharpProject.csproj"); + + using var workspace = CreateMSBuildWorkspace(); + var project = await workspace.OpenProjectAsync(referencingProjectPath); + + Assert.Empty(project.ProjectReferences); + + // Project referenced through ProjectReference with ReferenceOutputAssembly=false + // should be present in the solution. + Assert.NotNull(project.Solution.GetProjectsByName("CSharpProject").SingleOrDefault()); + } + private static FileSet VisitProjectReferences(FileSet files, Action visitProjectReference) { var result = new List<(string, object)>(); diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 74e9dc72f68ed..ac97eb85341e4 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -2,7 +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. +using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; @@ -20,98 +23,207 @@ internal abstract class AbstractAssetProvider /// /// return data of type T whose checksum is the given checksum /// - public abstract ValueTask GetAssetAsync(AssetHint assetHint, Checksum checksum, CancellationToken cancellationToken); + public abstract ValueTask GetAssetAsync(AssetPath assetPath, Checksum checksum, CancellationToken cancellationToken); + public abstract Task GetAssetsAsync(AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken); public async Task CreateSolutionInfoAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { - var solutionCompilationChecksums = await GetAssetAsync(AssetHint.None, solutionChecksum, cancellationToken).ConfigureAwait(false); - var solutionChecksums = await GetAssetAsync(AssetHint.None, solutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); - - var solutionAttributes = await GetAssetAsync(AssetHint.None, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); - - using var _ = ArrayBuilder.GetInstance(solutionChecksums.Projects.Length, out var projects); - foreach (var (projectChecksum, projectId) in solutionChecksums.Projects) - projects.Add(await CreateProjectInfoAsync(projectId, projectChecksum, cancellationToken).ConfigureAwait(false)); - - var analyzerReferences = await CreateCollectionAsync(AssetHint.None, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); - + var solutionCompilationChecksums = await GetAssetAsync(AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); + var solutionChecksums = await GetAssetAsync(AssetPathKind.SolutionStateChecksums, solutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + + var solutionAttributes = await GetAssetAsync(AssetPathKind.SolutionAttributes, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + await GetAssetAsync(AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + + // Fetch all the project state checksums up front. That allows getting all the data in a single call, and + // enables parallel fetching of the projects below. + using var _1 = ArrayBuilder>.GetInstance(solutionChecksums.Projects.Length, out var projectsTasks); + await this.GetAssetHelper().GetAssetsAsync( + AssetPathKind.ProjectStateChecksums, + solutionChecksums.Projects.Checksums, + static (_, projectStateChecksums, tuple) => tuple.projectsTasks.Add(tuple.@this.CreateProjectInfoAsync(projectStateChecksums, tuple.cancellationToken)), + (@this: this, projectsTasks, cancellationToken), + cancellationToken).ConfigureAwait(false); + + var analyzerReferences = await this.GetAssetsArrayAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + + // Fetch the projects in parallel. + var projects = await Task.WhenAll(projectsTasks).ConfigureAwait(false); return SolutionInfo.Create( - solutionAttributes.Id, solutionAttributes.Version, solutionAttributes.FilePath, projects.ToImmutableAndClear(), analyzerReferences).WithTelemetryId(solutionAttributes.TelemetryId); + solutionAttributes.Id, + solutionAttributes.Version, + solutionAttributes.FilePath, + ImmutableCollectionsMarshal.AsImmutableArray(projects), + analyzerReferences).WithTelemetryId(solutionAttributes.TelemetryId); } - public async Task CreateProjectInfoAsync(ProjectId projectId, Checksum projectChecksum, CancellationToken cancellationToken) + public async Task CreateProjectInfoAsync(ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) { - var projectChecksums = await GetAssetAsync(assetHint: projectId, projectChecksum, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfFalse(projectId == projectChecksums.ProjectId); + await Task.Yield(); + + var projectId = projectChecksums.ProjectId; - var attributes = await GetAssetAsync(assetHint: projectId, projectChecksums.Info, cancellationToken).ConfigureAwait(false); + var attributes = await GetAssetAsync(new(AssetPathKind.ProjectAttributes, projectId), projectChecksums.Info, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(RemoteSupportedLanguages.IsSupported(attributes.Language)); var compilationOptions = attributes.FixUpCompilationOptions( - await GetAssetAsync(assetHint: projectId, projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); - var parseOptions = await GetAssetAsync(assetHint: projectId, projectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false); + await GetAssetAsync(new(AssetPathKind.ProjectCompilationOptions, projectId), projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); + var parseOptionsTask = GetAssetAsync(new(AssetPathKind.ProjectParseOptions, projectId), projectChecksums.ParseOptions, cancellationToken); - var projectReferences = await CreateCollectionAsync(assetHint: projectId, projectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false); - var metadataReferences = await CreateCollectionAsync(assetHint: projectId, projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); - var analyzerReferences = await CreateCollectionAsync(assetHint: projectId, projectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var projectReferencesTask = this.GetAssetsArrayAsync(new(AssetPathKind.ProjectProjectReferences, projectId), projectChecksums.ProjectReferences, cancellationToken); + var metadataReferencesTask = this.GetAssetsArrayAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), projectChecksums.MetadataReferences, cancellationToken); + var analyzerReferencesTask = this.GetAssetsArrayAsync(new(AssetPathKind.ProjectAnalyzerReferences, projectId), projectChecksums.AnalyzerReferences, cancellationToken); - var documentInfos = await CreateDocumentInfosAsync(projectChecksums.Documents).ConfigureAwait(false); - var additionalDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments).ConfigureAwait(false); - var analyzerConfigDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AnalyzerConfigDocuments).ConfigureAwait(false); + // Attempt to fetch all the documents for this project in bulk. This will allow for all the data to be fetched + // efficiently. We can then go and create the DocumentInfos for each document in the project. + await SynchronizeProjectDocumentsAsync(projectChecksums, cancellationToken).ConfigureAwait(false); + + var documentInfosTask = CreateDocumentInfosAsync(projectChecksums.Documents); + var additionalDocumentInfosTask = CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments); + var analyzerConfigDocumentInfosTask = CreateDocumentInfosAsync(projectChecksums.AnalyzerConfigDocuments); return ProjectInfo.Create( attributes, compilationOptions, - parseOptions, - documentInfos, - projectReferences, - metadataReferences, - analyzerReferences, - additionalDocumentInfos, - analyzerConfigDocumentInfos, + await parseOptionsTask.ConfigureAwait(false), + await documentInfosTask.ConfigureAwait(false), + await projectReferencesTask.ConfigureAwait(false), + await metadataReferencesTask.ConfigureAwait(false), + await analyzerReferencesTask.ConfigureAwait(false), + await additionalDocumentInfosTask.ConfigureAwait(false), + await analyzerConfigDocumentInfosTask.ConfigureAwait(false), hostObjectType: null); // TODO: https://github.com/dotnet/roslyn/issues/62804 - async Task> CreateDocumentInfosAsync(ChecksumsAndIds checksumsAndIds) + async Task> CreateDocumentInfosAsync(DocumentChecksumsAndIds checksumsAndIds) { - using var _ = ArrayBuilder.GetInstance(checksumsAndIds.Length, out var documentInfos); + var documentInfos = new FixedSizeArrayBuilder(checksumsAndIds.Length); - foreach (var (documentChecksum, documentId) in checksumsAndIds) + foreach (var (attributeChecksum, textChecksum, documentId) in checksumsAndIds) { cancellationToken.ThrowIfCancellationRequested(); - documentInfos.Add(await CreateDocumentInfoAsync(documentId, documentChecksum, cancellationToken).ConfigureAwait(false)); + documentInfos.Add(await CreateDocumentInfoAsync(documentId, attributeChecksum, textChecksum, cancellationToken).ConfigureAwait(false)); } - return documentInfos.ToImmutableAndClear(); + return documentInfos.MoveToImmutable(); } } - public async Task CreateDocumentInfoAsync( - DocumentId documentId, Checksum documentChecksum, CancellationToken cancellationToken) + public async Task SynchronizeProjectDocumentsAsync( + ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) { - var documentSnapshot = await GetAssetAsync(assetHint: documentId, documentChecksum, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfTrue(documentId != documentSnapshot.DocumentId); + await Task.Yield(); + + using var _1 = PooledHashSet.GetInstance(out var attributeChecksums); + using var _2 = PooledHashSet.GetInstance(out var textChecksums); + + projectChecksums.Documents.AttributeChecksums.AddAllTo(attributeChecksums); + projectChecksums.AdditionalDocuments.AttributeChecksums.AddAllTo(attributeChecksums); + projectChecksums.AnalyzerConfigDocuments.AttributeChecksums.AddAllTo(attributeChecksums); - var attributes = await GetAssetAsync(assetHint: documentId, documentSnapshot.Info, cancellationToken).ConfigureAwait(false); - var serializableSourceText = await GetAssetAsync(assetHint: documentId, documentSnapshot.Text, cancellationToken).ConfigureAwait(false); + projectChecksums.Documents.TextChecksums.AddAllTo(textChecksums); + projectChecksums.AdditionalDocuments.TextChecksums.AddAllTo(textChecksums); + projectChecksums.AnalyzerConfigDocuments.TextChecksums.AddAllTo(textChecksums); - var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); - var textLoader = TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create(), attributes.FilePath)); + var attributesTask = this.GetAssetsAsync( + assetPath: new(AssetPathKind.DocumentAttributes, projectChecksums.ProjectId), + attributeChecksums, + cancellationToken); + + var textTask = this.GetAssetsAsync( + assetPath: new(AssetPathKind.DocumentText, projectChecksums.ProjectId), + textChecksums, + cancellationToken); + + await Task.WhenAll(attributesTask, textTask).ConfigureAwait(false); + } + + public async Task CreateDocumentInfoAsync( + DocumentId documentId, Checksum attributeChecksum, Checksum textChecksum, CancellationToken cancellationToken) + { + var attributes = await GetAssetAsync(new(AssetPathKind.DocumentAttributes, documentId), attributeChecksum, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await GetAssetAsync(new(AssetPathKind.DocumentText, documentId), textChecksum, cancellationToken).ConfigureAwait(false); + + var textLoader = serializableSourceText.ToTextLoader(attributes.FilePath); // TODO: do we need version? return new DocumentInfo(attributes, textLoader, documentServiceProvider: null); } - public async Task> CreateCollectionAsync( - AssetHint assetHint, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class + public AssetHelper GetAssetHelper() + => new(this); + + public readonly struct AssetHelper(AbstractAssetProvider assetProvider) { - using var _ = ArrayBuilder.GetInstance(checksums.Count, out var assets); + public Task GetAssetsAsync(AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) + => assetProvider.GetAssetsAsync(assetPath, checksums, callback, arg, cancellationToken); - foreach (var checksum in checksums) - { - cancellationToken.ThrowIfCancellationRequested(); - assets.Add(await GetAssetAsync(assetHint, checksum, cancellationToken).ConfigureAwait(false)); - } + public Task GetAssetsAsync(AssetPath assetPath, ChecksumCollection checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) + => assetProvider.GetAssetsAsync(assetPath, checksums, callback, arg, cancellationToken); + } +} - return assets.ToImmutableAndClear(); +internal static class AbstractAssetProviderExtensions +{ + public static Task GetAssetsAsync( + this AbstractAssetProvider assetProvider, AssetPath assetPath, HashSet checksums, CancellationToken cancellationToken) + { + return assetProvider.GetAssetsAsync( + assetPath, checksums, callback: null, arg: default, cancellationToken); + } + + public static Task GetAssetsAsync( + this AbstractAssetProvider assetProvider, AssetPath assetPath, ChecksumCollection checksums, CancellationToken cancellationToken) + { + return assetProvider.GetAssetsAsync( + assetPath, checksums, callback: null, arg: default, cancellationToken); + } + + public static async Task GetAssetsAsync( + this AbstractAssetProvider assetProvider, AssetPath assetPath, ChecksumCollection checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) + { + using var _1 = PooledHashSet.GetInstance(out var checksumSet); +#if NET + checksumSet.EnsureCapacity(checksums.Children.Length); +#endif + checksumSet.AddAll(checksums.Children); + + await assetProvider.GetAssetsAsync(assetPath, checksumSet, callback, arg, cancellationToken).ConfigureAwait(false); + } + + /// + /// Returns an array of assets, corresponding to all the checksums found in the given . + /// The assets will be returned in the order corresponding to their checksum in . + /// + public static async Task> GetAssetsArrayAsync( + this AbstractAssetProvider assetProvider, AssetPath assetPath, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class + { + // Note: nothing stops 'checksums' from having multiple identical checksums in it. First, collapse this down to + // a set so we're only asking about unique checksums. + using var _1 = PooledHashSet.GetInstance(out var checksumSet); +#if NET + checksumSet.EnsureCapacity(checksums.Children.Length); +#endif + checksumSet.AddAll(checksums.Children); + + using var _2 = PooledDictionary.GetInstance(out var checksumToAsset); + + await assetProvider.GetAssetHelper().GetAssetsAsync( + assetPath, checksumSet, + // Calling .Add here is safe. As checksum-set is a unique set of checksums, we'll never have collions here. + static (checksum, asset, checksumToAsset) => checksumToAsset.Add(checksum, asset), + checksumToAsset, + cancellationToken).ConfigureAwait(false); + + // Note: GetAssetsAsync will only succeed if we actually found all our assets (it crashes otherwise). So we can + // just safely assume we can index into checksumToAsset here. + Contract.ThrowIfTrue(checksumToAsset.Count != checksumSet.Count); + + // The result of GetAssetsArrayAsync wants the returned assets to be in the exact order of the checksums that + // were in 'checksums'. So now fetch the assets in that order, even if we found them in an entirely different + // order. + var result = new FixedSizeArrayBuilder(checksums.Children.Length); + foreach (var checksum in checksums.Children) + result.Add(checksumToAsset[checksum]); + + return result.MoveToImmutable(); } } diff --git a/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs b/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs index 71dfab8be7bec..d66c72ff84200 100644 --- a/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs @@ -4,6 +4,7 @@ using System; using Microsoft.ServiceHub.Framework; +using Nerdbank.Streams; using StreamJsonRpc; using static Microsoft.ServiceHub.Framework.ServiceJsonRpcDescriptor; @@ -71,6 +72,10 @@ protected override JsonRpcConnection CreateConnection(JsonRpc jsonRpc) public static readonly ServiceRpcDescriptor SolutionSnapshotProvider = CreateClientServiceDescriptor("SolutionSnapshotProvider", new Version(0, 1)); public static readonly ServiceRpcDescriptor DebuggerManagedHotReloadService = CreateDebuggerServiceDescriptor("ManagedHotReloadService", new Version(0, 1)); public static readonly ServiceRpcDescriptor HotReloadLoggerService = CreateDebuggerServiceDescriptor("HotReloadLogger", new Version(0, 1)); + public static readonly ServiceRpcDescriptor HotReloadSessionNotificationService = CreateDebuggerServiceDescriptor("HotReloadSessionNotificationService", new Version(0, 1)); + public static readonly ServiceRpcDescriptor ManagedHotReloadAgentManagerService = CreateDebuggerServiceDescriptor("ManagedHotReloadAgentManagerService", new Version(0, 1)); + public static readonly ServiceRpcDescriptor HotReloadOptionService = CreateDebuggerClientServiceDescriptor("HotReloadOptionService", new Version(0, 1)); + public static readonly ServiceRpcDescriptor MauiLaunchCustomizerService = CreateMauiServiceDescriptor("MauiLaunchCustomizerService", new Version(0, 1)); public static ServiceMoniker CreateMoniker(string namespaceName, string componentName, string serviceName, Version? version) => new(namespaceName + "." + componentName + "." + serviceName, version); @@ -78,22 +83,38 @@ public static ServiceMoniker CreateMoniker(string namespaceName, string componen /// /// Descriptor for services proferred by the client extension (implemented in TypeScript). /// - public static ServiceJsonRpcDescriptor CreateClientServiceDescriptor(string serviceName, Version? version) + public static ServiceJsonRpcDescriptor CreateClientServiceDescriptor(string serviceName, Version? version = null) => new ClientServiceDescriptor(CreateMoniker(LanguageServerComponentNamespace, LanguageClientComponentName, serviceName, version), clientInterface: null) .WithExceptionStrategy(ExceptionProcessing.ISerializable); /// - /// Descriptor for services proferred by Roslyn server (implemented in C#). + /// Descriptor for services proferred by Roslyn server or Visual Studio in-proc (implemented in C#). /// - public static ServiceJsonRpcDescriptor CreateServerServiceDescriptor(string serviceName, Version? version) + public static ServiceJsonRpcDescriptor CreateServerServiceDescriptor(string serviceName, Version? version = null) => CreateDescriptor(CreateMoniker(LanguageServerComponentNamespace, LanguageServerComponentName, serviceName, version)); /// /// Descriptor for services proferred by the debugger server (implemented in C#). /// - public static ServiceJsonRpcDescriptor CreateDebuggerServiceDescriptor(string serviceName, Version? version) + public static ServiceJsonRpcDescriptor CreateDebuggerServiceDescriptor(string serviceName, Version? version = null) => CreateDescriptor(CreateMoniker(VisualStudioComponentNamespace, DebuggerComponentName, serviceName, version)); + /// + /// Descriptor for services proferred by the debugger server (implemented in TypeScript). + /// + public static ServiceJsonRpcDescriptor CreateDebuggerClientServiceDescriptor(string serviceName, Version? version = null) + => new ClientServiceDescriptor(CreateMoniker(VisualStudioComponentNamespace, DebuggerComponentName, serviceName, version), clientInterface: null) + .WithExceptionStrategy(ExceptionProcessing.ISerializable); + + /// + /// Descriptor for services proferred by the MAUI extension (implemented in TypeScript). + /// + public static ServiceJsonRpcDescriptor CreateMauiServiceDescriptor(string serviceName, Version? version) + => new ServiceJsonRpcDescriptor(CreateMoniker(VisualStudioComponentNamespace, "Maui", serviceName, version), clientInterface: null, + Formatters.MessagePack, MessageDelimiters.BigEndianInt32LengthHeader, + new MultiplexingStream.Options { ProtocolMajorVersion = 3 }) + .WithExceptionStrategy(ExceptionProcessing.ISerializable); + private static ServiceJsonRpcDescriptor CreateDescriptor(ServiceMoniker moniker) => new ServiceJsonRpcDescriptor(moniker, Formatters.MessagePack, MessageDelimiters.BigEndianInt32LengthHeader) .WithExceptionStrategy(ExceptionProcessing.ISerializable); diff --git a/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs index e212b51ceeca4..4ed9ac92fe6fd 100644 --- a/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs +++ b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs @@ -109,7 +109,7 @@ private ValueTask BreakStateOrCapabilitiesChangedAsync(bool? inBreakState, Cance try { Contract.ThrowIfNull(_debuggingSession); - encService.BreakStateOrCapabilitiesChanged(_debuggingSession.Value, inBreakState, out _); + encService.BreakStateOrCapabilitiesChanged(_debuggingSession.Value, inBreakState); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -143,7 +143,7 @@ public ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) _committedDesignTimeSolution = committedDesignTimeSolution; - encService.CommitSolutionUpdate(_debuggingSession.Value, out _); + encService.CommitSolutionUpdate(_debuggingSession.Value); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -186,7 +186,7 @@ public ValueTask EndSessionAsync(CancellationToken cancellationToken) { Contract.ThrowIfNull(_debuggingSession); - encService.EndDebuggingSession(_debuggingSession.Value, out _); + encService.EndDebuggingSession(_debuggingSession.Value); _debuggingSession = null; _committedDesignTimeSolution = null; diff --git a/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageServiceDescriptor.cs b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageServiceDescriptor.cs new file mode 100644 index 0000000000000..cbc2270653ac9 --- /dev/null +++ b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageServiceDescriptor.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using Microsoft.CodeAnalysis.BrokeredServices; +using Microsoft.ServiceHub.Framework; + +namespace Microsoft.CodeAnalysis.EditAndContinue; + +internal static class ManagedHotReloadLanguageServiceDescriptor +{ + private const string ServiceName = "ManagedHotReloadLanguageService"; + public const string ServiceVersion = "0.1"; + public const string MonikerName = BrokeredServiceDescriptors.LanguageServerComponentNamespace + "." + BrokeredServiceDescriptors.LanguageServerComponentName + "." + ServiceName; + + public static readonly ServiceJsonRpcDescriptor Descriptor = BrokeredServiceDescriptors.CreateServerServiceDescriptor(ServiceName, new(ServiceVersion)); + + static ManagedHotReloadLanguageServiceDescriptor() + => Debug.Assert(Descriptor.Moniker.Name == MonikerName); +} diff --git a/src/Workspaces/Remote/Core/IOnServiceBrokerInitialized.cs b/src/Workspaces/Remote/Core/IOnServiceBrokerInitialized.cs new file mode 100644 index 0000000000000..b47826b50f630 --- /dev/null +++ b/src/Workspaces/Remote/Core/IOnServiceBrokerInitialized.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.ServiceHub.Framework; + +namespace Microsoft.CodeAnalysis.BrokeredServices; + +/// +/// Allow services to export IOnServiceBrokerInitialized and getting called back when service broker is initialized +/// +internal interface IOnServiceBrokerInitialized +{ + void OnServiceBrokerInitialized(IServiceBroker serviceBroker); +} diff --git a/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs index 0b3c0f3a0bc77..5982dfe6dd27d 100644 --- a/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs @@ -3,19 +3,36 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; -namespace Microsoft.CodeAnalysis.Remote +namespace Microsoft.CodeAnalysis.Remote; + +internal interface IRemoteAssetSynchronizationService { - internal interface IRemoteAssetSynchronizationService - { - /// - /// Synchronize data to OOP proactively so that the corresponding solution is often already available when - /// features call into it. - /// - ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, int workspaceVersion, CancellationToken cancellationToken); - ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, IEnumerable textChanges, CancellationToken cancellationToken); - } + /// + /// Synchronize data to OOP proactively so that the corresponding solution is often already available when features + /// call into it. + /// + ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken); + + /// + /// Synchronize the text changes made by a user to a particular document as they are editing it. By sending over + /// the text changes as they happen, we can attempt to 'prime' the remote asset cache with a final that is built based off of retrieving the remote source text with a checksum corresponding + /// to and then applying the to it. Then, when + /// the next remote call comes in for the new solution snapshot, it can hopefully just pluck that text out of the + /// cache without having to sync the entire contents of the file over. + /// + ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, ImmutableArray textChanges, CancellationToken cancellationToken); + + /// + /// Synchronize over what the user's current active document is that they're editing. This can then be used by the + /// remote side to help determine which documents are best to strongly hold onto data for, and which should just + /// hold on weakly. Given how much work happens on the active document, this can help avoid the remote side from + /// continually creating and then throwing away that data. + /// + ValueTask SynchronizeActiveDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs b/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs index b57bf95fcb2b2..3c37c867d4b49 100644 --- a/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs @@ -22,9 +22,9 @@ internal interface ISolutionAssetProvider /// The writer to write the assets into. Implementations of this method must call on it (in the event of failure or success). Failing to do so will lead to hangs on /// the code that reads from the corresponding side of this. - /// Optional project and document ids to scope the search for checksums down to. This can + /// Optional project and document ids to scope the search for checksums down to. This can /// save substantially on performance by avoiding having to search the full solution tree to find matching items for /// a particular checksum. ValueTask WriteAssetsAsync( - PipeWriter pipeWriter, Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, CancellationToken cancellationToken); + PipeWriter pipeWriter, Checksum solutionChecksum, AssetPath assetPath, ReadOnlyMemory checksums, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj b/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj index 2a2b29384f61b..05f63516078fa 100644 --- a/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj +++ b/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj @@ -63,6 +63,7 @@ + diff --git a/src/Workspaces/Remote/Core/RemoteCallback.cs b/src/Workspaces/Remote/Core/RemoteCallback.cs index e40309b6cc045..eebafafb93834 100644 --- a/src/Workspaces/Remote/Core/RemoteCallback.cs +++ b/src/Workspaces/Remote/Core/RemoteCallback.cs @@ -33,10 +33,10 @@ public RemoteCallback(T callback) /// /// Use to perform a callback from ServiceHub process to an arbitrary brokered service hosted in the original process (usually devenv). /// - public static async ValueTask InvokeServiceAsync( + public static async ValueTask InvokeServiceAsync( ServiceBrokerClient client, ServiceRpcDescriptor serviceDescriptor, - Func, CancellationToken, ValueTask> invocation, + Func, CancellationToken, ValueTask> invocation, CancellationToken cancellationToken) { ServiceBrokerClient.Rental rental; @@ -54,7 +54,7 @@ public static async ValueTask InvokeServiceAsync( Contract.ThrowIfNull(rental.Proxy); var callback = new RemoteCallback(rental.Proxy); - return await invocation(callback, cancellationToken).ConfigureAwait(false); + await invocation(callback, cancellationToken).ConfigureAwait(false); } /// @@ -97,9 +97,9 @@ public async ValueTask InvokeAsync(FuncA callback to asynchronously read data. The callback should not complete the , but no harm will happen if it does. /// A cancellation token the operation will observe. - public async ValueTask InvokeAsync( + public async ValueTask InvokeAsync( Func invocation, - Func> reader, + Func reader, CancellationToken cancellationToken) { try @@ -118,13 +118,15 @@ public async ValueTask InvokeAsync( // use fire-and-forget here and avoids us having to consider things like async-tracking-tokens for // testing purposes. await Task.WhenAll(writeTask, readTask).ConfigureAwait(false); - return await readTask.ConfigureAwait(false); + await readTask.ConfigureAwait(false); } catch (Exception exception) when (ReportUnexpectedException(exception, cancellationToken)) { throw new OperationCanceledIgnoringCallerTokenException(exception); } + return; + async Task WriteAsync(T service, PipeWriter pipeWriter) { Exception? exception = null; @@ -170,7 +172,7 @@ async Task WriteAsync(T service, PipeWriter pipeWriter) } } - async Task ReadAsync(PipeReader pipeReader) + async Task ReadAsync(PipeReader pipeReader) { // NOTE: it is intentional that the try/catch pattern here does NOT match the one in WriteAsync. There // are very different semantics around each. The writer code passes ownership to StreamJsonRPC, while @@ -180,7 +182,8 @@ async Task ReadAsync(PipeReader pipeReader) Exception? exception = null; try { - return await reader(pipeReader, cancellationToken).ConfigureAwait(false); + await reader(pipeReader, cancellationToken).ConfigureAwait(false); + return; } catch (Exception ex) when ((exception = ex) == null) { diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs new file mode 100644 index 0000000000000..7bedab5bb1271 --- /dev/null +++ b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs @@ -0,0 +1,135 @@ +// Licensed to the .NET Foundation under one or more 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.Buffers; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Serialization; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote; + +/// +/// See for an explanation of the wire format we use when communicating assets +/// between the host and our OOP server. This implements the code for reading assets transmitted over the wire. has the code for writing assets. +/// +internal readonly struct RemoteHostAssetReader( + PipeReader pipeReader, + Checksum solutionChecksum, + int objectCount, + ISerializerService serializer, + Action callback, + TArg arg) +{ + private readonly PipeReader _pipeReader = pipeReader; + private readonly Checksum _solutionChecksum = solutionChecksum; + private readonly int _objectCount = objectCount; + private readonly ISerializerService _serializer = serializer; + private readonly Action _callback = callback; + private readonly TArg _arg = arg; + + public ValueTask ReadDataAsync(CancellationToken cancellationToken) + { + // Suppress ExecutionContext flow for asynchronous operations operate on the pipe. 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 BLOCK LEXICALLY (it's fine to await within the call to + // ReadDataSuppressedFlowAsync). The Dispose method that restores ExecutionContext flow must run on the same + // thread where SuppressFlow was originally run. + using var _ = FlowControlHelper.TrySuppressFlow(); + return ReadDataSuppressedFlowAsync(cancellationToken); + } + + private async ValueTask ReadDataSuppressedFlowAsync(CancellationToken cancellationToken) + { + using var pipeReaderStream = _pipeReader.AsStream(leaveOpen: true); + + // Get an object reader over the stream. Note: we do not check the validation bytes here as the stream is + // currently pointing at header data prior to the object data. Instead, we will check the validation bytes + // prior to reading each asset out. + using var objectReader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, checkValidationBytes: false); + + // Ensure that no invariants were broken and that both sides of the communication channel are talking about the + // same pinned solution. + var responseSolutionChecksum = await ReadChecksumFromPipeReaderAsync(cancellationToken).ConfigureAwait(false); + Contract.ThrowIfFalse(_solutionChecksum == responseSolutionChecksum); + + // Now actually read all the messages we expect to get. + for (var i = 0; i < _objectCount; i++) + await ReadSingleMessageAsync(objectReader, cancellationToken).ConfigureAwait(false); + } + + private async ValueTask ReadSingleMessageAsync(ObjectReader objectReader, CancellationToken cancellationToken) + { + // For each message, read the sentinel byte and the length of the data chunk we'll be reading. + var length = await CheckSentinelByteAndReadLengthAsync(cancellationToken).ConfigureAwait(false); + + // Now buffer in the rest of the data we need to read. Because we're reading as much data in as we'll need to + // consume, all further reading (for this single item) can handle synchronously without worrying about this + // blocking the reading thread on cross-process pipe io. + var fillReadResult = await _pipeReader.ReadAtLeastAsync(length, cancellationToken).ConfigureAwait(false); + + // Note: we have let the pipe reader know that we're done with 'read at least' call, but that we haven't + // consumed anything from it yet. Otherwise it will throw that another read can't start the objectReader + // reading calls below. + _pipeReader.AdvanceTo(fillReadResult.Buffer.Start); + + // Let the object reader do it's own individual object checking. + objectReader.CheckValidationBytes(); + + // Now do the actual read of the data, synchronously, from the buffers that are now in memory within our + // process. These reads will move the pipe-reader forward, without causing any blocking on async-io. + var checksum = Checksum.ReadFrom(objectReader); + var kind = (WellKnownSynchronizationKind)objectReader.ReadByte(); + + var asset = _serializer.Deserialize(kind, objectReader, cancellationToken); + Contract.ThrowIfNull(asset); + _callback(checksum, (T)asset, _arg); + } + + private async ValueTask CheckSentinelByteAndReadLengthAsync(CancellationToken cancellationToken) + { + const int HeaderSize = sizeof(byte) + sizeof(int); + + var lengthReadResult = await _pipeReader.ReadAtLeastAsync(HeaderSize, cancellationToken).ConfigureAwait(false); + var (sentinelByte, length) = ReadSentinelAndLength(lengthReadResult); + + // Check that the sentinel is correct, and move the pipe reader forward to the end of the header. + Contract.ThrowIfTrue(sentinelByte != RemoteHostAssetWriter.MessageSentinelByte); + _pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(HeaderSize)); + + return length; + } + + // Note on Checksum itself as it depends on SequenceReader, which is provided by nerdbank.streams on netstandard2.0 + // (which the Workspace layer does not depend on). + private async ValueTask ReadChecksumFromPipeReaderAsync(CancellationToken cancellationToken) + { + var readChecksumResult = await _pipeReader.ReadAtLeastAsync(Checksum.HashSize, cancellationToken).ConfigureAwait(false); + + var checksum = ReadChecksum(readChecksumResult); + _pipeReader.AdvanceTo(readChecksumResult.Buffer.GetPosition(Checksum.HashSize)); + return checksum; + } + + private static (byte, int) ReadSentinelAndLength(ReadResult readResult) + { + var sequenceReader = new SequenceReader(readResult.Buffer); + Contract.ThrowIfFalse(sequenceReader.TryRead(out var sentinel)); + Contract.ThrowIfFalse(sequenceReader.TryReadLittleEndian(out int length)); + return (sentinel, length); + } + + private static Checksum ReadChecksum(ReadResult readResult) + { + var sequenceReader = new SequenceReader(readResult.Buffer); + Span checksumBytes = stackalloc byte[Checksum.HashSize]; + Contract.ThrowIfFalse(sequenceReader.TryCopyTo(checksumBytes)); + return Checksum.From(checksumBytes); + } +} diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs deleted file mode 100644 index b7a04d068ce19..0000000000000 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ /dev/null @@ -1,113 +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.IO.Pipelines; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Serialization; -using Nerdbank.Streams; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Remote -{ - internal static class RemoteHostAssetSerialization - { - public static async ValueTask WriteDataAsync( - Stream stream, - Dictionary assetMap, - ISerializerService serializer, - SolutionReplicationContext context, - Checksum solutionChecksum, - ReadOnlyMemory checksums, - CancellationToken cancellationToken) - { - using var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken); - - // This information is not actually needed on the receiving end. However, we still send it so that the - // receiver can assert that both sides are talking about the same solution snapshot and no weird invariant - // breaks have occurred. - solutionChecksum.WriteTo(writer); - - // special case - if (checksums.Length == 0) - return; - - Debug.Assert(assetMap != null); - - for (var i = 0; i < checksums.Span.Length; i++) - { - var checksum = checksums.Span[i]; - var asset = assetMap[checksum]; - - // We flush after each item as that forms a reasonably sized chunk of data to want to then send over the - // pipe for the reader on the other side to read. This allows the item-writing to remain entirely - // synchronous without any blocking on async flushing, while also ensuring that we're not buffering the - // entire stream of data into the pipe before it gets sent to the other side. - WriteAsset(writer, serializer, context, asset, cancellationToken); - await stream.FlushAsync(cancellationToken).ConfigureAwait(false); - } - - return; - - static void WriteAsset(ObjectWriter writer, ISerializerService serializer, SolutionReplicationContext context, object asset, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(asset); - var kind = asset.GetWellKnownSynchronizationKind(); - writer.WriteInt32((int)kind); - - serializer.Serialize(asset, writer, context, cancellationToken); - } - } - - public static ValueTask> ReadDataAsync( - PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, CancellationToken cancellationToken) - { - // Suppress ExecutionContext flow for asynchronous operations operate on the pipe. 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 ReadDataSuppressedFlowAsync(pipeReader, solutionChecksum, objectCount, serializerService, cancellationToken); - - static async ValueTask> ReadDataSuppressedFlowAsync( - PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, CancellationToken cancellationToken) - { - using var stream = await pipeReader.AsPrebufferedStreamAsync(cancellationToken).ConfigureAwait(false); - return ReadData(stream, solutionChecksum, objectCount, serializerService, cancellationToken); - } - } - - public static ImmutableArray ReadData(Stream stream, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(objectCount, out var results); - - using var reader = ObjectReader.GetReader(stream, leaveOpen: true, cancellationToken); - - // Ensure that no invariants were broken and that both sides of the communication channel are talking about - // the same pinned solution. - var responseSolutionChecksum = Checksum.ReadFrom(reader); - Contract.ThrowIfFalse(solutionChecksum == responseSolutionChecksum); - - for (int i = 0, n = objectCount; i < n; i++) - { - var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); - - // in service hub, cancellation means simply closed stream - var result = serializerService.Deserialize(kind, reader, cancellationToken); - Contract.ThrowIfNull(result); - results.Add(result); - } - - return results.ToImmutableAndClear(); - } - } -} diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs new file mode 100644 index 0000000000000..a43e259eab20a --- /dev/null +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote; + +using static SerializableBytes; +using static SolutionAssetStorage; +using ChecksumAndAsset = (Checksum checksum, object asset); + +/// +/// Contains the utilities for writing assets from the host to a pipe-writer and for reading those assets on the +/// server. The format we use is as follows. For each asset we're writing we write: +/// +/// ------------------------------------------------------------------------- +/// | sentinel (1 byte) | length of data (4 bytes) | data (variable length) | +/// ------------------------------------------------------------------------- +/// +/// The writing code will write out the sentinel-byte and data-length, ensuring it is flushed to the pipe-writer. This +/// allows the pipe-reader to immediately read that information so it can then pre-allocate the space for the data to go +/// into. After writing the data the writer will also flush, so the reader can then read the data out of the pipe into +/// its buffer. Once present in the reader's buffer, synchronous deserialization can happen without any sync-over-async +/// blocking on async-io. +/// The sentinel byte serves to let us detect immediately on the reading side if something has gone wrong with +/// this system. +/// In order to be able to write out the data-length, the writer will first synchronously write the asset to an +/// in-memory buffer, then write that buffer's length to the pipe-writer, then copy the in-memory buffer to the writer. +/// When writing/reading the data-segment, we use an the / +/// subsystem. This will write its own validation bits, and then the data describing the asset. This data is: +/// +/// ---------------------------------------------------------------------------------------------------------- +/// | data (variable length) | +/// ---------------------------------------------------------------------------------------------------------- +/// | ObjectWriter validation (2 bytes) | checksum (16 bytes) | kind (1 byte) | asset-data (asset specified) | +/// ---------------------------------------------------------------------------------------------------------- +/// +/// The validation bytes are followed by the checksum. The checksum is needed in the message as assets can be found in +/// any order (they are not reported in the order of the array of checksums passed into the writing method). Following +/// this is the kind of the asset. This kind is used by the reading code to know which asset-deserialization routine to +/// invoke. Finally, the asset data itself is written out. +/// +internal readonly struct RemoteHostAssetWriter( + PipeWriter pipeWriter, Scope scope, AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService serializer) +{ + /// + /// A sentinel byte we place between messages. Ensures we can detect when something has gone wrong as soon as + /// possible. Note: the value we pick is neither ascii nor extended ascii. So it's very unlikely to appear + /// accidentally. + /// + public const byte MessageSentinelByte = 0b10010000; + + private static readonly ObjectPool s_streamPool = new(() => new()); + + private readonly PipeWriter _pipeWriter = pipeWriter; + private readonly Scope _scope = scope; + private readonly AssetPath _assetPath = assetPath; + private readonly ReadOnlyMemory _checksums = checksums; + private readonly ISerializerService _serializer = serializer; + + public Task WriteDataAsync(CancellationToken cancellationToken) + => ProducerConsumer.RunAsync( + ProducerConsumerOptions.SingleReaderWriterOptions, + produceItems: static (onItemFound, args) => args.@this.FindAssetsAsync(onItemFound, args.cancellationToken), + consumeItems: static (items, args) => args.@this.WriteBatchToPipeAsync(items, args.cancellationToken), + args: (@this: this, cancellationToken), + cancellationToken); + + private Task FindAssetsAsync(Action onItemFound, CancellationToken cancellationToken) + => _scope.FindAssetsAsync( + _assetPath, _checksums, + static (checksum, asset, onItemFound) => onItemFound((checksum, asset)), + onItemFound, cancellationToken); + + private async Task WriteBatchToPipeAsync( + IAsyncEnumerable checksumsAndAssets, CancellationToken cancellationToken) + { + // Get the in-memory buffer and object-writer we'll use to serialize the assets into. Don't write any + // validation bytes at this point in time. We'll write them between each asset we write out. Using a single + // object writer across all assets means we get the benefit of string deduplication across all assets we write + // out. + using var pooledStream = s_streamPool.GetPooledObject(); + using var objectWriter = new ObjectWriter(pooledStream.Object, leaveOpen: true, writeValidationBytes: false); + + // This information is not actually needed on the receiving end. However, we still send it so that the receiver + // can assert that both sides are talking about the same solution snapshot and no weird invariant breaks have + // occurred. + _scope.SolutionChecksum.WriteTo(_pipeWriter); + await _pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + + // Keep track of how many checksums we found. We must find all the checksums we were asked to find. + var foundChecksumCount = 0; + + await foreach (var (checksum, asset) in checksumsAndAssets) + { + await WriteSingleAssetToPipeAsync( + pooledStream.Object, objectWriter, checksum, asset, cancellationToken).ConfigureAwait(false); + foundChecksumCount++; + } + + cancellationToken.ThrowIfCancellationRequested(); + + // If we weren't canceled, we better have found and written out all the expected assets. + Contract.ThrowIfTrue(foundChecksumCount != _checksums.Length); + } + + private async ValueTask WriteSingleAssetToPipeAsync( + ReadWriteStream tempStream, ObjectWriter objectWriter, Checksum checksum, object asset, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(asset); + + // We're about to send a message. Write out our sentinel byte to ensure the reading side can detect problems + // with our writing. + WriteSentinelByteToPipeWriter(); + + // Write the asset to a temporary buffer so we can calculate its length. Note: as this is an in-memory + // temporary buffer, we don't have to worry about synchronous writes on it blocking on the pipe-writer. Instead, + // we'll handle the pipe-writing ourselves afterwards in a completely async fashion. + WriteAssetToTempStream(tempStream, objectWriter, checksum, asset, cancellationToken); + + // Write the length of the asset to the pipe writer so the reader knows how much data to read. + WriteTempStreamLengthToPipeWriter(tempStream); + + // Ensure we flush out the length so the reading side can immediately read the header to determine how much data + // to it will need to prebuffer. + await _pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + + // Now, asynchronously copy the temp buffer over to the writer stream. + tempStream.Position = 0; + await tempStream.CopyToAsync(_pipeWriter, cancellationToken).ConfigureAwait(false); + + // We flush after each item as that forms a reasonably sized chunk of data to want to then send over the pipe + // for the reader on the other side to read. This allows the item-writing to remain entirely synchronous + // without any blocking on async flushing, while also ensuring that we're not buffering the entire stream of + // data into the pipe before it gets sent to the other side. + await _pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + private void WriteAssetToTempStream( + ReadWriteStream tempStream, ObjectWriter objectWriter, Checksum checksum, object asset, CancellationToken cancellationToken) + { + // Reset the temp stream to the beginning and clear it out. Don't truncate the stream as we're going to be + // writing to it multiple times. This will allow us to reuse the internal chunks of the buffer, without having + // to reallocate them over and over again. Note: this stream internally keeps a list of byte[]s that it writes + // to. Each byte[] is less than the LOH size, so there's no concern about LOH fragmentation here. + tempStream.Position = 0; + tempStream.SetLength(0, truncate: false); + + // Write out the object writer validation bytes. This will help us detect issues when reading if we've screwed + // something up. + objectWriter.WriteValidationBytes(); + + // Write the checksum for the asset we're writing out, so the other side knows what asset this is. + checksum.WriteTo(objectWriter); + + // Write out the kind so the receiving end knows how to deserialize this asset. + objectWriter.WriteByte((byte)asset.GetWellKnownSynchronizationKind()); + + // Now serialize out the asset itself. + _serializer.Serialize(asset, objectWriter, cancellationToken); + } + + private void WriteSentinelByteToPipeWriter() + { + var span = _pipeWriter.GetSpan(1); + span[0] = MessageSentinelByte; + _pipeWriter.Advance(1); + } + + private void WriteTempStreamLengthToPipeWriter(ReadWriteStream tempStream) + { + var length = tempStream.Length; + Contract.ThrowIfTrue(length > int.MaxValue); + + var span = _pipeWriter.GetSpan(sizeof(int)); + BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); + _pipeWriter.Advance(sizeof(int)); + } +} diff --git a/src/Workspaces/Remote/Core/RemoteProcessConfiguration.cs b/src/Workspaces/Remote/Core/RemoteProcessConfiguration.cs index 9907fee627cde..86d25a5d2b7a0 100644 --- a/src/Workspaces/Remote/Core/RemoteProcessConfiguration.cs +++ b/src/Workspaces/Remote/Core/RemoteProcessConfiguration.cs @@ -4,19 +4,13 @@ using System; -namespace Microsoft.CodeAnalysis.Remote -{ - [Flags] - internal enum RemoteProcessConfiguration - { - /// - /// Remote host runs on .NET 6+. - /// - Core = 1, +namespace Microsoft.CodeAnalysis.Remote; - /// - /// Remote host uses server GC. - /// - ServerGC = 1 << 1, - } +[Flags] +internal enum RemoteProcessConfiguration +{ + /// + /// Remote host uses server GC. + /// + ServerGC = 1, } diff --git a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx index eb93919aea4f5..fd418d75e0cfb 100644 --- a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx +++ b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx @@ -228,4 +228,7 @@ Source generation + + Semantic search + \ No newline at end of file diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs index c375fee080ccb..f635780e0b524 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -26,6 +26,7 @@ using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.NavigationBar; using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.SemanticSearch; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SourceGeneration; using Microsoft.CodeAnalysis.StackTraceExplorer; @@ -84,10 +85,11 @@ internal sealed class ServiceDescriptors (typeof(IRemoteStackTraceExplorerService), null), (typeof(IRemoteUnitTestingSearchService), null), (typeof(IRemoteSourceGenerationService), null), + (typeof(IRemoteSemanticSearchService), typeof(IRemoteSemanticSearchService.ICallback)), }); internal readonly RemoteSerializationOptions Options; - private readonly ImmutableDictionary _descriptors; + private readonly ImmutableDictionary _descriptors; private readonly string _componentName; private readonly Func _featureDisplayNameProvider; @@ -113,17 +115,15 @@ internal static string GetSimpleName(Type serviceInterface) return interfaceName.Substring(InterfaceNamePrefix.Length, interfaceName.Length - InterfaceNamePrefix.Length - InterfaceNameSuffix.Length); } - private (ServiceDescriptor, ServiceDescriptor, ServiceDescriptor, ServiceDescriptor) CreateDescriptors(Type serviceInterface, Type? callbackInterface) + private (ServiceDescriptor descriptorCoreClr64, ServiceDescriptor descriptorCoreClr64ServerGC) CreateDescriptors(Type serviceInterface, Type? callbackInterface) { Contract.ThrowIfFalse(callbackInterface == null || callbackInterface.IsInterface); var simpleName = GetSimpleName(serviceInterface); - var descriptor64 = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, Suffix64, Options, _featureDisplayNameProvider, callbackInterface); - var descriptor64ServerGC = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, Suffix64 + SuffixServerGC, Options, _featureDisplayNameProvider, callbackInterface); var descriptorCoreClr64 = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, SuffixCoreClr + Suffix64, Options, _featureDisplayNameProvider, callbackInterface); var descriptorCoreClr64ServerGC = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, SuffixCoreClr + Suffix64 + SuffixServerGC, Options, _featureDisplayNameProvider, callbackInterface); - return (descriptor64, descriptor64ServerGC, descriptorCoreClr64, descriptorCoreClr64ServerGC); + return (descriptorCoreClr64, descriptorCoreClr64ServerGC); } public static bool IsCurrentProcessRunningOnCoreClr() @@ -131,17 +131,20 @@ public static bool IsCurrentProcessRunningOnCoreClr() !RuntimeInformation.FrameworkDescription.StartsWith(".NET Native"); public ServiceDescriptor GetServiceDescriptorForServiceFactory(Type serviceType) - => GetServiceDescriptor(serviceType, RemoteProcessConfiguration.ServerGC | (IsCurrentProcessRunningOnCoreClr() ? RemoteProcessConfiguration.Core : 0)); + => GetServiceDescriptor(serviceType, RemoteProcessConfiguration.ServerGC); public ServiceDescriptor GetServiceDescriptor(Type serviceType, RemoteProcessConfiguration configuration) { - var (descriptor64, descriptor64ServerGC, descriptorCoreClr64, descriptorCoreClr64ServerGC) = _descriptors[serviceType]; - return (configuration & (RemoteProcessConfiguration.Core | RemoteProcessConfiguration.ServerGC)) switch + if (!_descriptors.TryGetValue(serviceType, out var descriptor)) { - 0 => descriptor64, - RemoteProcessConfiguration.Core => descriptorCoreClr64, - RemoteProcessConfiguration.ServerGC => descriptor64ServerGC, - RemoteProcessConfiguration.Core | RemoteProcessConfiguration.ServerGC => descriptorCoreClr64ServerGC, + throw ExceptionUtilities.UnexpectedValue(serviceType); + } + + var (descriptorCoreClr64, descriptorCoreClr64ServerGC) = descriptor; + return (configuration & RemoteProcessConfiguration.ServerGC) switch + { + 0 => descriptorCoreClr64, + RemoteProcessConfiguration.ServerGC => descriptorCoreClr64ServerGC, _ => throw ExceptionUtilities.Unreachable() }; } @@ -162,7 +165,7 @@ internal readonly struct TestAccessor internal TestAccessor(ServiceDescriptors serviceDescriptors) => _serviceDescriptors = serviceDescriptors; - public ImmutableDictionary Descriptors + public ImmutableDictionary Descriptors => _serviceDescriptors._descriptors; } } diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 52a3f75670cc7..9ed631830ecc8 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Buffers; -using System.IO; using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; @@ -12,221 +10,57 @@ using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Remote -{ - /// - /// Provides solution assets present locally (in the current process) to a remote process where the solution is being replicated to. - /// - internal sealed class SolutionAssetProvider(SolutionServices services) : ISolutionAssetProvider - { - public const string ServiceName = "SolutionAssetProvider"; - - internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceDescriptors.ComponentName, ServiceName, suffix: "", ServiceDescriptors.GetFeatureDisplayName); - - private readonly SolutionServices _services = services; - - public ValueTask WriteAssetsAsync( - PipeWriter pipeWriter, - Checksum solutionChecksum, - AssetHint assetHint, - ReadOnlyMemory checksums, - CancellationToken cancellationToken) - { - // Suppress ExecutionContext flow for asynchronous operations operate on the pipe. 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 WriteAssetsSuppressedFlowAsync(pipeWriter, solutionChecksum, assetHint, checksums, cancellationToken); - - async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, CancellationToken cancellationToken) - { - // The responsibility is on us (as per the requirements of RemoteCallback.InvokeAsync) to Complete the - // pipewriter. This will signal to streamjsonrpc that the writer passed into it is complete, which will - // allow the calling side know to stop reading results. - Exception? exception = null; - try - { - await WriteAssetsWorkerAsync(pipeWriter, solutionChecksum, assetHint, checksums, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) when ((exception = ex) == null) - { - throw ExceptionUtilities.Unreachable(); - } - finally - { - await pipeWriter.CompleteAsync(exception).ConfigureAwait(false); - } - } - } - - private async ValueTask WriteAssetsWorkerAsync( - PipeWriter pipeWriter, - Checksum solutionChecksum, - AssetHint assetHint, - ReadOnlyMemory checksums, - CancellationToken cancellationToken) - { - var assetStorage = _services.GetRequiredService().AssetStorage; - var serializer = _services.GetRequiredService(); - var scope = assetStorage.GetScope(solutionChecksum); +namespace Microsoft.CodeAnalysis.Remote; - using var _ = Creator.CreateResultMap(out var resultMap); - - await scope.AddAssetsAsync(assetHint, checksums, resultMap, cancellationToken).ConfigureAwait(false); +/// +/// Provides solution assets present locally (in the current process) to a remote process where the solution is being replicated to. +/// +internal sealed class SolutionAssetProvider(SolutionServices services) : ISolutionAssetProvider +{ + public const string ServiceName = "SolutionAssetProvider"; - cancellationToken.ThrowIfCancellationRequested(); + internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceDescriptors.ComponentName, ServiceName, suffix: "", ServiceDescriptors.GetFeatureDisplayName); - using var stream = new PipeWriterStream(pipeWriter); - await RemoteHostAssetSerialization.WriteDataAsync( - stream, resultMap, serializer, scope.ReplicationContext, - solutionChecksum, checksums, cancellationToken).ConfigureAwait(false); - } + private readonly SolutionAssetStorage _assetStorage = services.GetRequiredService().AssetStorage; + private readonly ISerializerService _serializer = services.GetRequiredService(); - /// - /// Simple port of - /// https://github.com/AArnott/Nerdbank.Streams/blob/dafeb5846702bc29e261c9ddf60f42feae01654c/src/Nerdbank.Streams/BufferWriterStream.cs#L16. - /// Wraps a in a interface. Preferred over as that API produces a stream that will synchronously flush after - /// every write. That's undesirable as that will then block a thread pool thread on the actual - /// asynchronous flush call to the underlying PipeWriter - /// - /// - /// Note: this stream does not have to the underlying it - /// is holding onto (including within , , or ). - /// Responsibility for that is solely in the hands of . - /// - private class PipeWriterStream : Stream, IDisposableObservable + public ValueTask WriteAssetsAsync( + PipeWriter pipeWriter, + Checksum solutionChecksum, + AssetPath assetPath, + ReadOnlyMemory checksums, + CancellationToken cancellationToken) + { + // Suppress ExecutionContext flow for asynchronous operations operate on the pipe. 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 BLOCK LEXICALLY (it's fine to await within the call to + // WriteAssetsSuppressedFlowAsync). The Dispose method that restores ExecutionContext flow must run on the same + // thread where SuppressFlow was originally run. + using var _ = FlowControlHelper.TrySuppressFlow(); + return WriteAssetsSuppressedFlowAsync(pipeWriter, solutionChecksum, assetPath, checksums, cancellationToken); + + async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum solutionChecksum, AssetPath assetPath, ReadOnlyMemory checksums, CancellationToken cancellationToken) { - private readonly PipeWriter _writer; - - public bool IsDisposed { get; private set; } - - public override bool CanRead => false; - public override bool CanSeek => false; - public override bool CanWrite => !this.IsDisposed; - - internal PipeWriterStream(PipeWriter writer) - { - _writer = writer; - } - - protected override void Dispose(bool disposing) - { - this.IsDisposed = true; - base.Dispose(disposing); - - // DO NOT CALL .Complete on the PipeWriter here (see remarks on type). - } - - private Exception ThrowDisposedOr(Exception ex) - { - Verify.NotDisposed(this); - throw ex; - } - - /// - /// Intentionally a no op. We know that we and - /// will call at appropriate times to ensure data is being sent through the writer - /// at a reasonable cadence (once per asset). - /// - public override void Flush() - { - Verify.NotDisposed(this); - - // DO NOT CALL .Complete on the PipeWriter here (see remarks on type). - } - - public override async Task FlushAsync(CancellationToken cancellationToken) - { - await _writer.FlushAsync(cancellationToken).ConfigureAwait(false); - - // DO NOT CALL .Complete on the PipeWriter here (see remarks on type). - } - - public override void Write(byte[] buffer, int offset, int count) - { - Requires.NotNull(buffer, nameof(buffer)); - Verify.NotDisposed(this); - -#if NET - _writer.Write(buffer.AsSpan(offset, count)); -#else - var span = _writer.GetSpan(count); - buffer.AsSpan(offset, count).CopyTo(span); - _writer.Advance(count); -#endif - } - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - this.Write(buffer, offset, count); - return Task.CompletedTask; - } - - public override void WriteByte(byte value) + // The responsibility is on us (as per the requirements of RemoteCallback.InvokeAsync) to Complete the + // pipewriter. This will signal to streamjsonrpc that the writer passed into it is complete, which will + // allow the calling side know to stop reading results. + Exception? exception = null; + try { - Verify.NotDisposed(this); - var span = _writer.GetSpan(1); - span[0] = value; - _writer.Advance(1); + var scope = _assetStorage.GetScope(solutionChecksum); + var writer = new RemoteHostAssetWriter(pipeWriter, scope, assetPath, checksums, _serializer); + await writer.WriteDataAsync(cancellationToken).ConfigureAwait(false); } - -#if NET - - public override void Write(ReadOnlySpan buffer) + catch (Exception ex) when ((exception = ex) == null) { - Verify.NotDisposed(this); - _writer.Write(buffer); + throw ExceptionUtilities.Unreachable(); } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) + finally { - cancellationToken.ThrowIfCancellationRequested(); - this.Write(buffer.Span); - return default; + await pipeWriter.CompleteAsync(exception).ConfigureAwait(false); } - -#endif - - #region read/seek api (not supported) - - public override long Length => throw this.ThrowDisposedOr(new NotSupportedException()); - public override long Position - { - get => throw this.ThrowDisposedOr(new NotSupportedException()); - set => this.ThrowDisposedOr(new NotSupportedException()); - } - - public override int Read(byte[] buffer, int offset, int count) - => throw this.ThrowDisposedOr(new NotSupportedException()); - - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => throw this.ThrowDisposedOr(new NotSupportedException()); - -#if NET - - public override int Read(Span buffer) - => throw this.ThrowDisposedOr(new NotSupportedException()); - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) - => throw this.ThrowDisposedOr(new NotSupportedException()); - -#endif - - public override int ReadByte() - => throw this.ThrowDisposedOr(new NotSupportedException()); - - public override long Seek(long offset, SeekOrigin origin) - => throw this.ThrowDisposedOr(new NotSupportedException()); - - public override void SetLength(long value) - => this.ThrowDisposedOr(new NotSupportedException()); - - #endregion } } } diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index ce07e27e23cb1..845f823512bf4 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Serialization; @@ -26,12 +25,6 @@ internal sealed partial class Scope( public readonly ProjectCone? ProjectCone = projectCone; public readonly SolutionCompilationState CompilationState = compilationState; - /// - /// Will be disposed from when the last ref-count to this scope goes - /// away. - /// - public readonly SolutionReplicationContext ReplicationContext = new(); - /// /// Only safe to read write while is held. /// @@ -44,28 +37,36 @@ public void Dispose() /// Retrieve assets of specified available within from /// the storage. /// - public async Task AddAssetsAsync( - AssetHint assetHint, + public async Task FindAssetsAsync( + AssetPath assetPath, ReadOnlyMemory checksums, - Dictionary assetMap, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using var obj = Creator.CreateChecksumSet(checksums); - var checksumsToFind = obj.Object; + using var _ = SharedPools.Default>().GetPooledObject(out var checksumsToFind); + AddChecksums(checksums, checksumsToFind); var numberOfChecksumsToSearch = checksumsToFind.Count; Contract.ThrowIfTrue(checksumsToFind.Contains(Checksum.Null)); - await FindAssetsAsync(assetHint, checksumsToFind, assetMap, cancellationToken).ConfigureAwait(false); + await FindAssetsAsync(assetPath, checksumsToFind, onAssetFound, arg, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(checksumsToFind.Count > 0); - Contract.ThrowIfTrue(assetMap.Count != numberOfChecksumsToSearch); + + return; + + static void AddChecksums(ReadOnlyMemory checksums, HashSet checksumsToFind) + { + foreach (var checksum in checksums.Span) + checksumsToFind.Add(checksum); + } } - private async Task FindAssetsAsync( - AssetHint assetHint, HashSet remainingChecksumsToFind, Dictionary result, CancellationToken cancellationToken) + private async Task FindAssetsAsync( + AssetPath assetPath, HashSet remainingChecksumsToFind, Action onAssetFound, TArg arg, CancellationToken cancellationToken) { var solutionState = this.CompilationState; @@ -74,13 +75,13 @@ private async Task FindAssetsAsync( // If we're not in a project cone, start the search at the top most state-checksum corresponding to the // entire solution. Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } else { // Otherwise, grab the top-most state checksum for this cone and search within that. Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(this.ProjectCone.RootProjectId, out var stateChecksums)); - await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } @@ -97,16 +98,20 @@ public async ValueTask GetAssetAsync(Checksum checksum, CancellationToke { Contract.ThrowIfTrue(checksum == Checksum.Null); - using var checksumPool = Creator.CreateChecksumSet(checksum); - using var _ = Creator.CreateResultMap(out var resultPool); + var checksums = new ReadOnlyMemory([checksum]); - await scope.FindAssetsAsync(AssetHint.None, checksumPool.Object, resultPool, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfTrue(resultPool.Count != 1); + object? asset = null; + await scope.FindAssetsAsync(AssetPath.FullLookupForTesting, checksums, (foundChecksum, foundAsset, _) => + { + Contract.ThrowIfNull(foundAsset); + Contract.ThrowIfTrue(asset != null); // We should only find one asset + Contract.ThrowIfTrue(checksum != foundChecksum); + asset = foundAsset; + }, default(VoidResult), cancellationToken).ConfigureAwait(false); - var (resultingChecksum, value) = resultPool.First(); - Contract.ThrowIfFalse(checksum == resultingChecksum); + Contract.ThrowIfNull(asset); - return value; + return asset; } } } diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs index 841b07901f7a8..d5f80f5030c92 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -110,8 +111,6 @@ private void DecreaseScopeRefCount(Scope scope) // Last ref went away, update our maps while under the lock, then cleanup its context data outside of the lock. _checksumToScope.Remove(solutionChecksum); } - - scope.ReplicationContext.Dispose(); } internal TestAccessor GetTestAccessor() @@ -130,25 +129,5 @@ public async ValueTask GetRequiredAssetAsync(Checksum checksum, Cancella { return await _solutionAssetStorage._checksumToScope.Single().Value.GetTestAccessor().GetAssetAsync(checksum, cancellationToken).ConfigureAwait(false); } - - public bool IsPinned(Checksum checksum) - { - lock (_solutionAssetStorage._gate) - { - return _solutionAssetStorage._checksumToScope.TryGetValue(checksum, out var scope) && - scope.RefCount >= 1; - } - } - - public int PinnedScopesCount - { - get - { - lock (_solutionAssetStorage._gate) - { - return _solutionAssetStorage._checksumToScope.Count; - } - } - } } } diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf index 4bfac139d4ece..3aa7be77cf0d7 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf @@ -127,6 +127,11 @@ Sémantická klasifikace + + Semantic search + Semantic search + + Asset provider Poskytovatel prostředků diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf index 43f902907eec6..5ae865fc5b8fd 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf @@ -127,6 +127,11 @@ Semantische Klassifizierung + + Semantic search + Semantic search + + Asset provider Objektanbieter diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf index 8ffd6ab4b1a91..c1929b307024e 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf @@ -127,6 +127,11 @@ Clasificación semántica + + Semantic search + Semantic search + + Asset provider Proveedor de recursos diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf index 9b75da6666dd9..9dcc51bd94980 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf @@ -127,6 +127,11 @@ Classification sémantique + + Semantic search + Semantic search + + Asset provider Fournisseur de ressources diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf index 4c22af8b51f15..5b2dcec1e03dd 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf @@ -127,6 +127,11 @@ Classificazione semantica + + Semantic search + Semantic search + + Asset provider Provider di asset diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf index 759c13e1fd037..a848834c20024 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf @@ -127,6 +127,11 @@ セマンティック分類 + + Semantic search + Semantic search + + Asset provider 資産プロバイダー diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf index f98b7cce44952..6f466ead35540 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf @@ -127,6 +127,11 @@ 의미 체계 분류 + + Semantic search + Semantic search + + Asset provider 자산 공급자 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf index cfe2fc8c9fcdf..a7f318568feab 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf @@ -127,6 +127,11 @@ Klasyfikacja semantyczna + + Semantic search + Semantic search + + Asset provider Dostawca elementów zawartości diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf index a6dab4774b523..aec02cfff9b61 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf @@ -127,6 +127,11 @@ Classificação semântica + + Semantic search + Semantic search + + Asset provider Provedor de ativos diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf index caedc701ca520..016f14ba153aa 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf @@ -127,6 +127,11 @@ Семантическая классификация + + Semantic search + Semantic search + + Asset provider Поставщик ресурсов diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf index 21895d278ba16..38a8a3610fa45 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf @@ -127,6 +127,11 @@ Anlamsal sınıflandırma + + Semantic search + Semantic search + + Asset provider Varlık sağlayıcısı diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf index c40948f2ced01..89f3b235ad703 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf @@ -127,6 +127,11 @@ 语义分类 + + Semantic search + Semantic search + + Asset provider 资产提供商 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf index 01cff839e5be5..fe5c28929bb15 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf @@ -127,6 +127,11 @@ 語意分類 + + Semantic search + Semantic search + + Asset provider 資產提供者 diff --git a/src/Workspaces/Remote/ServiceHub/ExternalAccess/Razor/Api/RazorBrokeredServiceImplementation.cs b/src/Workspaces/Remote/ServiceHub/ExternalAccess/Razor/Api/RazorBrokeredServiceImplementation.cs index 115d9761186a2..78750306c73f5 100644 --- a/src/Workspaces/Remote/ServiceHub/ExternalAccess/Razor/Api/RazorBrokeredServiceImplementation.cs +++ b/src/Workspaces/Remote/ServiceHub/ExternalAccess/Razor/Api/RazorBrokeredServiceImplementation.cs @@ -18,10 +18,6 @@ public static ValueTask RunServiceAsync(Func implementation, CancellationToken cancellationToken) => BrokeredServiceBase.RunServiceImplAsync(implementation, cancellationToken); - [Obsolete("Use RunServiceAsync (that is passsed a Solution) instead", error: false)] - public static ValueTask GetSolutionAsync(this RazorPinnedSolutionInfoWrapper solutionInfo, ServiceBrokerClient client, CancellationToken cancellationToken) - => RemoteWorkspaceManager.Default.GetSolutionAsync(client, solutionInfo.UnderlyingObject, cancellationToken); - public static ValueTask RunServiceAsync(this RazorPinnedSolutionInfoWrapper solutionInfo, ServiceBrokerClient client, Func> implementation, CancellationToken cancellationToken) => RemoteWorkspaceManager.Default.RunServiceAsync(client, solutionInfo.UnderlyingObject, implementation, cancellationToken); } diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index fd72dfbe20d76..56c0b37c6e900 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -5,10 +5,9 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; @@ -22,7 +21,7 @@ namespace Microsoft.CodeAnalysis.Remote; internal sealed partial class AssetProvider(Checksum solutionChecksum, SolutionAssetCache assetCache, IAssetSource assetSource, ISerializerService serializerService) : AbstractAssetProvider { - private const int PooledChecksumArraySize = 256; + private const int PooledChecksumArraySize = 1024; private static readonly ObjectPool s_checksumPool = new(() => new Checksum[PooledChecksumArraySize], 16); private readonly Checksum _solutionChecksum = solutionChecksum; @@ -31,7 +30,7 @@ internal sealed partial class AssetProvider(Checksum solutionChecksum, SolutionA private readonly IAssetSource _assetSource = assetSource; public override async ValueTask GetAssetAsync( - AssetHint assetHint, Checksum checksum, CancellationToken cancellationToken) + AssetPath assetPath, Checksum checksum, CancellationToken cancellationToken) { Contract.ThrowIfTrue(checksum == Checksum.Null); if (_assetCache.TryGetAsset(checksum, out var asset)) @@ -40,61 +39,84 @@ public override async ValueTask GetAssetAsync( using var _1 = PooledHashSet.GetInstance(out var checksums); checksums.Add(checksum); - using var _2 = PooledDictionary.GetInstance(out var results); - await this.SynchronizeAssetsAsync(assetHint, checksums, results, cancellationToken).ConfigureAwait(false); + using var _2 = ArrayBuilder.GetInstance(1, out var builder); + await this.GetAssetHelper().GetAssetsAsync( + assetPath, checksums, + static (_, asset, builder) => builder.Add(asset), + builder, cancellationToken).ConfigureAwait(false); - return (T)results[checksum]; + Contract.ThrowIfTrue(builder.Count != 1); + + return builder[0]; } - public async ValueTask> GetAssetsAsync( - AssetHint assetHint, HashSet checksums, CancellationToken cancellationToken) + public override async Task GetAssetsAsync( + AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) where TArg : default { - using var _ = PooledDictionary.GetInstance(out var results); - - // bulk synchronize checksums first - var syncer = new ChecksumSynchronizer(this); - await syncer.SynchronizeAssetsAsync(assetHint, checksums, results, cancellationToken).ConfigureAwait(false); - - var result = new (Checksum checksum, T asset)[checksums.Count]; - var index = 0; - foreach (var (checksum, assetObject) in results) - { - result[index] = (checksum, (T)assetObject); - index++; - } - - return ImmutableCollectionsMarshal.AsImmutableArray(result); + await this.SynchronizeAssetsAsync(assetPath, checksums, callback, arg, cancellationToken).ConfigureAwait(false); } + /// + /// This is the function called when we are not doing an incremental update, but are instead doing a bulk + /// full sync. + /// public async ValueTask SynchronizeSolutionAssetsAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { - var timer = new Stopwatch(); - timer.Start(); + var timer = SharedStopwatch.StartNew(); - // this will pull in assets that belong to the given solution checksum to this remote host. - // this one is not supposed to be used for functionality but only for perf. that is why it doesn't return anything. - // to get actual data GetAssetAsync should be used. and that will return actual data and if there is any missing data in cache, GetAssetAsync - // itself will bring that data in from data source (VS) + // this will pull in assets that belong to the given solution checksum to this remote host. this one is not + // supposed to be used for functionality but only for perf. that is why it doesn't return anything. to get + // actual data GetAssetAsync should be used. and that will return actual data and if there is any missing data + // in cache, GetAssetAsync itself will bring that data in from data source (VS) - // one can call this method to make cache hot for all assets that belong to the solution checksum so that GetAssetAsync call will most likely cache hit. - // it is most likely since we might change cache hueristic in future which make data to live a lot shorter in the cache, and the data might get expired - // before one actually consume the data. + // one can call this method to make cache hot for all assets that belong to the solution checksum so that + // GetAssetAsync call will most likely cache hit. it is most likely since we might change cache heuristic in + // future which make data to live a lot shorter in the cache, and the data might get expired before one actually + // consume the data. using (Logger.LogBlock(FunctionId.AssetService_SynchronizeSolutionAssetsAsync, Checksum.GetChecksumLogInfo, solutionChecksum, cancellationToken)) { - var syncer = new ChecksumSynchronizer(this); - await syncer.SynchronizeSolutionAssetsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); + await SynchronizeSolutionAssetsWorkerAsync().ConfigureAwait(false); } - timer.Stop(); - // report telemetry to help correlate slow solution sync with UI delays - if (timer.ElapsedMilliseconds > 1000) + var elapsed = timer.Elapsed; + if (elapsed.TotalMilliseconds > 1000) + Logger.Log(FunctionId.AssetService_Perf, KeyValueLogMessage.Create(map => map["SolutionSyncTime"] = elapsed.TotalMilliseconds)); + + async ValueTask SynchronizeSolutionAssetsWorkerAsync() { - Logger.Log(FunctionId.AssetService_Perf, KeyValueLogMessage.Create(map => map["SolutionSyncTime"] = timer.ElapsedMilliseconds)); + // first, get top level solution state for the given solution checksum + var compilationStateChecksums = await this.GetAssetAsync( + AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); + + // then grab the information we want off of the compilation state object. + var solutionStateChecksum = await this.GetAssetAsync( + AssetPathKind.SolutionStateChecksums, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + + // Then grab the data we need off of hte state checksums + using var _1 = ArrayBuilder.GetInstance(out var tasks); + tasks.Add(this.GetAssetAsync( + AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, compilationStateChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).AsTask()); + tasks.Add(this.GetAssetAsync( + AssetPathKind.SolutionAttributes, solutionStateChecksum.Attributes, cancellationToken).AsTask()); + tasks.Add(this.GetAssetsAsync( + AssetPathKind.SolutionAnalyzerReferences, solutionStateChecksum.AnalyzerReferences, cancellationToken)); + await Task.WhenAll(tasks).ConfigureAwait(false); + + using var _3 = ArrayBuilder.GetInstance(out var allProjectStateChecksums); + await this.GetAssetHelper().GetAssetsAsync( + AssetPathKind.ProjectStateChecksums, + solutionStateChecksum.Projects.Checksums, + static (_, asset, allProjectStateChecksums) => allProjectStateChecksums.Add(asset), + arg: allProjectStateChecksums, cancellationToken).ConfigureAwait(false); + + // fourth, get all projects and documents in the solution + await SynchronizeProjectAssetsAsync(allProjectStateChecksums, cancellationToken).ConfigureAwait(false); } } - public async ValueTask SynchronizeProjectAssetsAsync(ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) + public async ValueTask SynchronizeProjectAssetsAsync( + ArrayBuilder allProjectChecksums, CancellationToken cancellationToken) { // this will pull in assets that belong to the given project checksum to this remote host. this one is not // supposed to be used for functionality but only for perf. that is why it doesn't return anything. to get @@ -105,15 +127,110 @@ public async ValueTask SynchronizeProjectAssetsAsync(ProjectStateChecksums proje // GetAssetAsync call will most likely cache hit. it is most likely since we might change cache heuristic in // future which make data to live a lot shorter in the cache, and the data might get expired before one actually // consume the data. - using (Logger.LogBlock(FunctionId.AssetService_SynchronizeProjectAssetsAsync, Checksum.GetProjectChecksumsLogInfo, projectChecksums, cancellationToken)) + using (Logger.LogBlock(FunctionId.AssetService_SynchronizeProjectAssetsAsync, message: null, cancellationToken)) + { + // It's common to have two usage patterns of SynchronizeProjectAssetsAsync. Bulk syncing the majority of the + // solution over, or just syncing a single project (or small set of projects) in response to a small change + // (like a user edit). For the bulk case, we want to make sure we're doing as few round trips as possible, + // getting as much of the data we can in each call. For the single project case though, we don't want to + // have the host have to search the entire solution graph for data we know it contained within just that + // project. + // + // So, we split up our strategy here based on how many projects we're syncing. If it's 4 or less, we just + // sync each project individually, passing the data to the host so it can limit its search to just that + // project. If it's more than that, we do it in bulk, knowing that as we're searching for a ton of + // data, it's fine for the host to do a full pass for each of the data types we're looking for. + if (allProjectChecksums.Count <= 4) + { + // Still sync the N projects in parallel. + using var _ = ArrayBuilder.GetInstance(allProjectChecksums.Count, out var tasks); + foreach (var singleProjectChecksums in allProjectChecksums) + { + // Make a fresh singleton array, containing just this project checksum, and pass into the helper + // below. That way we can have just a single helper for actually doing the syncing, regardless of if + // we are are doing a single project or multiple. + ArrayBuilder.GetInstance(capacity: 1, out var tempBuffer); + tempBuffer.Add(singleProjectChecksums); + + // We want to synchronize the assets just for this project. So we can pass the ProjectId as a hint + // to limit the search on the host side. + tasks.Add(SynchronizeProjectAssetsWorkerAsync( + tempBuffer, singleProjectChecksums.ProjectId, freeArrayBuilder: true, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + } + else + { + // We want to synchronize all assets in bulk. Because of this, we can't narrow the search on the host + // side to a particular ProjectId. + await SynchronizeProjectAssetsWorkerAsync( + allProjectChecksums, + projectId: null, + freeArrayBuilder: false, cancellationToken).ConfigureAwait(false); + } + } + } + + private async Task SynchronizeProjectAssetsWorkerAsync( + ArrayBuilder allProjectChecksums, ProjectId? projectId, bool freeArrayBuilder, CancellationToken cancellationToken) + { + try + { + await Task.Yield(); + + using var _ = ArrayBuilder.GetInstance(out var tasks); + + // Make parallel requests for all the project data across all projects at once. For each request, pass + // in the appropriate info to let the search avoid looking at data unnecessarily. + tasks.Add(SynchronizeProjectAssetAsync(new(AssetPathKind.ProjectAttributes, projectId), static p => p.Info)); + tasks.Add(SynchronizeProjectAssetAsync(new(AssetPathKind.ProjectCompilationOptions, projectId), static p => p.CompilationOptions)); + tasks.Add(SynchronizeProjectAssetAsync(new(AssetPathKind.ProjectParseOptions, projectId), static p => p.ParseOptions)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(new(AssetPathKind.ProjectProjectReferences, projectId), static p => p.ProjectReferences)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), static p => p.MetadataReferences)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(new(AssetPathKind.ProjectAnalyzerReferences, projectId), static p => p.AnalyzerReferences)); + + // Then sync each project's documents in parallel with each other. + foreach (var projectChecksums in allProjectChecksums) + tasks.Add(SynchronizeProjectDocumentsAsync(projectChecksums, cancellationToken)); + + await Task.WhenAll(tasks).ConfigureAwait(false); + } + finally + { + if (freeArrayBuilder) + allProjectChecksums.Free(); + } + + return; + + Task SynchronizeProjectAssetAsync(AssetPath assetPath, Func getChecksum) + => SynchronizeProjectAssetOrCollectionAsync>( + assetPath, + static (projectStateChecksums, checksums, getChecksum) => checksums.Add(getChecksum(projectStateChecksums)), + getChecksum); + + Task SynchronizeProjectAssetCollectionAsync(AssetPath assetPath, Func getChecksums) + => SynchronizeProjectAssetOrCollectionAsync>( + assetPath, + static (projectStateChecksums, checksums, getChecksums) => getChecksums(projectStateChecksums).AddAllTo(checksums), + getChecksums); + + async Task SynchronizeProjectAssetOrCollectionAsync( + AssetPath assetPath, Action, TArg> addAllChecksums, TArg arg) { - var syncer = new ChecksumSynchronizer(this); - await syncer.SynchronizeProjectAssetsAsync(projectChecksums, cancellationToken).ConfigureAwait(false); + await Task.Yield(); + using var _ = PooledHashSet.GetInstance(out var checksums); + + foreach (var projectChecksums in allProjectChecksums) + addAllChecksums(projectChecksums, checksums, arg); + + await this.GetAssetsAsync(assetPath, checksums, cancellationToken).ConfigureAwait(false); } } - public async ValueTask SynchronizeAssetsAsync( - AssetHint assetHint, HashSet checksums, Dictionary? results, CancellationToken cancellationToken) + private async ValueTask SynchronizeAssetsAsync( + AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) { Contract.ThrowIfTrue(checksums.Contains(Checksum.Null)); if (checksums.Count == 0) @@ -133,77 +250,70 @@ public async ValueTask SynchronizeAssetsAsync( var usePool = missingChecksumsCount <= PooledChecksumArraySize; var missingChecksums = usePool ? s_checksumPool.Allocate() : new Checksum[missingChecksumsCount]; - missingChecksumsCount = 0; - foreach (var checksum in checksums) + try { - if (_assetCache.TryGetAsset(checksum, out var existing)) + missingChecksumsCount = 0; + foreach (var checksum in checksums) { - AddResult(checksum, existing); - } - else - { - if (missingChecksumsCount == missingChecksums.Length) + if (_assetCache.TryGetAsset(checksum, out var existing)) { - // This can happen if the asset cache has been modified by another thread during this method's execution. - var newMissingChecksums = new Checksum[missingChecksumsCount * 2]; - Array.Copy(missingChecksums, newMissingChecksums, missingChecksumsCount); - - if (usePool) + callback?.Invoke(checksum, existing, arg!); + } + else + { + if (missingChecksumsCount == missingChecksums.Length) { - s_checksumPool.Free(missingChecksums); - usePool = false; + // This can happen if the asset cache has been modified by another thread during this method's execution. + var newMissingChecksums = new Checksum[missingChecksumsCount * 2]; + Array.Copy(missingChecksums, newMissingChecksums, missingChecksumsCount); + + if (usePool) + { + s_checksumPool.Free(missingChecksums); + usePool = false; + } + + missingChecksums = newMissingChecksums; } - missingChecksums = newMissingChecksums; + missingChecksums[missingChecksumsCount] = checksum; + missingChecksumsCount++; } - - missingChecksums[missingChecksumsCount] = checksum; - missingChecksumsCount++; } - } - if (missingChecksumsCount > 0) - { - var missingChecksumsMemory = new ReadOnlyMemory(missingChecksums, 0, missingChecksumsCount); - var missingAssets = await RequestAssetsAsync(assetHint, missingChecksumsMemory, cancellationToken).ConfigureAwait(false); - - Contract.ThrowIfTrue(missingChecksumsMemory.Length != missingAssets.Length); - - for (int i = 0, n = missingAssets.Length; i < n; i++) + if (missingChecksumsCount > 0) { - var missingChecksum = missingChecksums[i]; - var missingAsset = missingAssets[i]; + var missingChecksumsMemory = new ReadOnlyMemory(missingChecksums, 0, missingChecksumsCount); + Contract.ThrowIfTrue(missingChecksumsMemory.Length == 0); - AddResult(missingChecksum, missingAsset); - _assetCache.GetOrAdd(missingChecksum, missingAsset); - } - } - - if (usePool) - s_checksumPool.Free(missingChecksums); - } - - return; - - void AddResult(Checksum checksum, object result) - { - if (results != null) - results[checksum] = result; - } - } - - private async ValueTask> RequestAssetsAsync( - AssetHint assetHint, ReadOnlyMemory checksums, CancellationToken cancellationToken) - { #if NETCOREAPP - Contract.ThrowIfTrue(checksums.Span.Contains(Checksum.Null)); + Contract.ThrowIfTrue(missingChecksumsMemory.Span.Contains(Checksum.Null)); #else - Contract.ThrowIfTrue(checksums.Span.IndexOf(Checksum.Null) >= 0); + Contract.ThrowIfTrue(missingChecksumsMemory.Span.IndexOf(Checksum.Null) >= 0); #endif - if (checksums.Length == 0) - return []; - - return await _assetSource.GetAssetsAsync(_solutionChecksum, assetHint, checksums, _serializerService, cancellationToken).ConfigureAwait(false); + await _assetSource.GetAssetsAsync( + _solutionChecksum, assetPath, missingChecksumsMemory, _serializerService, + static ( + Checksum missingChecksum, + T missingAsset, + (AssetProvider assetProvider, Checksum[] missingChecksums, Action? callback, TArg? arg) tuple) => + { + // Add to cache, returning the existing asset if it was added by another thread. + missingAsset = (T)tuple.assetProvider._assetCache.GetOrAdd(missingChecksum, missingAsset!); + + // Let our caller know about the asset if they're asking for it. + tuple.callback?.Invoke(missingChecksum, missingAsset, tuple.arg!); + }, + (this, missingChecksums, callback, arg), + cancellationToken).ConfigureAwait(false); + } + } + finally + { + if (usePool) + s_checksumPool.Free(missingChecksums); + } + } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs b/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs deleted file mode 100644 index abf8190c6b10d..0000000000000 --- a/src/Workspaces/Remote/ServiceHub/Host/ChecksumSynchronizer.cs +++ /dev/null @@ -1,126 +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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Serialization; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Remote; - -internal sealed partial class AssetProvider -{ - private readonly struct ChecksumSynchronizer(AssetProvider assetProvider) - { - // make sure there is always only 1 bulk synchronization - private static readonly SemaphoreSlim s_gate = new(initialCount: 1); - - private readonly AssetProvider _assetProvider = assetProvider; - - public async ValueTask SynchronizeAssetsAsync( - AssetHint assetHint, - HashSet checksums, - Dictionary? results, - CancellationToken cancellationToken) - { - using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - await _assetProvider.SynchronizeAssetsAsync(assetHint, checksums, results, cancellationToken).ConfigureAwait(false); - } - } - - public async ValueTask SynchronizeSolutionAssetsAsync(Checksum solutionChecksum, CancellationToken cancellationToken) - { - SolutionStateChecksums solutionChecksumObject; - using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - // this will make 4 round trip to data source (VS) to get all assets that belong to the given solution checksum - - // first, get solution checksum object for the given solution checksum - var solutionCompilationChecksumObject = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, solutionChecksum, cancellationToken).ConfigureAwait(false); - solutionChecksumObject = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, solutionCompilationChecksumObject.SolutionState, cancellationToken).ConfigureAwait(false); - - // second, get direct children of the solution - { - using var _ = PooledHashSet.GetInstance(out var checksums); - - solutionChecksumObject.AddAllTo(checksums); - checksums.Remove(solutionChecksumObject.Checksum); - await _assetProvider.SynchronizeAssetsAsync(assetHint: AssetHint.None, checksums, results: null, cancellationToken).ConfigureAwait(false); - } - } - - // third and last get direct children for all projects and documents in the solution - foreach (var (projectChecksum, _) in solutionChecksumObject.Projects) - { - // These GetAssetAsync calls should be fast since they were just retrieved above. There's a small - // chance the asset-cache GC pass may have cleaned them up, but that should be exceedingly rare. - var projectStateChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, projectChecksum, cancellationToken).ConfigureAwait(false); - await SynchronizeProjectAssetsAsync(projectStateChecksums, cancellationToken).ConfigureAwait(false); - } - } - - public async ValueTask SynchronizeProjectAssetsAsync(ProjectStateChecksums projectChecksum, CancellationToken cancellationToken) - { - using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - await SynchronizeProjectAssets_NoLockAsync(projectChecksum, cancellationToken).ConfigureAwait(false); - } - } - - private async ValueTask SynchronizeProjectAssets_NoLockAsync(ProjectStateChecksums projectChecksum, CancellationToken cancellationToken) - { - // get children of project checksum objects at once - using var _ = PooledHashSet.GetInstance(out var checksums); - - checksums.Add(projectChecksum.Info); - checksums.Add(projectChecksum.CompilationOptions); - checksums.Add(projectChecksum.ParseOptions); - AddAll(checksums, projectChecksum.ProjectReferences); - AddAll(checksums, projectChecksum.MetadataReferences); - AddAll(checksums, projectChecksum.AnalyzerReferences); - AddAll(checksums, projectChecksum.Documents.Checksums); - AddAll(checksums, projectChecksum.AdditionalDocuments.Checksums); - AddAll(checksums, projectChecksum.AnalyzerConfigDocuments.Checksums); - - // First synchronize all the top-level info about this project. - await _assetProvider.SynchronizeAssetsAsync( - assetHint: projectChecksum.ProjectId, checksums, results: null, cancellationToken).ConfigureAwait(false); - - checksums.Clear(); - - // Then synchronize the info about all the documents within. - await CollectChecksumChildrenAsync(this, projectChecksum.Documents.Checksums).ConfigureAwait(false); - await CollectChecksumChildrenAsync(this, projectChecksum.AdditionalDocuments.Checksums).ConfigureAwait(false); - await CollectChecksumChildrenAsync(this, projectChecksum.AnalyzerConfigDocuments.Checksums).ConfigureAwait(false); - - await _assetProvider.SynchronizeAssetsAsync( - assetHint: projectChecksum.ProjectId, checksums, results: null, cancellationToken).ConfigureAwait(false); - - async ValueTask CollectChecksumChildrenAsync(ChecksumSynchronizer @this, ChecksumCollection collection) - { - foreach (var checksum in collection) - { - // These GetAssetAsync calls should be fast since they were just retrieved above. There's a small - // chance the asset-cache GC pass may have cleaned them up, but that should be exceedingly rare. - var checksumObject = await @this._assetProvider.GetAssetAsync( - assetHint: projectChecksum.ProjectId, checksum, cancellationToken).ConfigureAwait(false); - checksums.Add(checksumObject.Info); - checksums.Add(checksumObject.Text); - } - } - } - - private static void AddAll(HashSet checksums, ChecksumCollection checksumCollection) - { - foreach (var checksum in checksumCollection) - checksums.Add(checksum); - } - } -} diff --git a/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs index 840e043daa46a..f9f08c0580ac1 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs @@ -3,7 +3,6 @@ // 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.Serialization; @@ -15,6 +14,13 @@ namespace Microsoft.CodeAnalysis.Remote; /// internal interface IAssetSource { - ValueTask> GetAssetsAsync( - Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, ISerializerService serializerService, CancellationToken cancellationToken); + /// Will be called back once per checksum in in the exact order of that array. + ValueTask GetAssetsAsync( + Checksum solutionChecksum, + AssetPath assetPath, + ReadOnlyMemory checksums, + ISerializerService serializerService, + Action callback, + TArg arg, + CancellationToken cancellationToken); } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs index 75660559eabdc..d5b3c3be1ef77 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -158,6 +159,12 @@ public void ReportTelemetry() })); } + public void AddAllTo(HashSet solutions) + { + foreach (var node in _cacheNodes) + solutions.AddIfNotNull(node.Solution); + } + private sealed class CacheNode(TChecksum checksum) { public readonly TChecksum Checksum = checksum; diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 1e9954a80bed1..5c23f42a6faa0 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using ICSharpCode.Decompiler.Solution; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; @@ -37,12 +37,12 @@ private readonly struct SolutionCreator(HostServices hostServices, AssetProvider public async Task IsIncrementalUpdateAsync(Checksum newSolutionChecksum, CancellationToken cancellationToken) { var newSolutionCompilationChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionChecksum, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionCompilationStateChecksums, newSolutionChecksum, cancellationToken).ConfigureAwait(false); var newSolutionChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionStateChecksums, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); var newSolutionInfo = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionAttributes, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); // if either solution id or file path changed, then we consider it as new solution return _baseSolution.Id == newSolutionInfo.Id && _baseSolution.FilePath == newSolutionInfo.FilePath; @@ -58,17 +58,18 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca // if needed again later. solution = solution.WithoutFrozenSourceGeneratedDocuments(); - var oldSolutionCompilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); var newSolutionCompilationChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionChecksum, cancellationToken).ConfigureAwait(false); - var oldSolutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionCompilationStateChecksums, newSolutionChecksum, cancellationToken).ConfigureAwait(false); var newSolutionChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionStateChecksums, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + + var oldSolutionCompilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + var oldSolutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); if (oldSolutionChecksums.Attributes != newSolutionChecksums.Attributes) { var newSolutionInfo = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionAttributes, newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); // if either id or file path has changed, then this is not update Contract.ThrowIfFalse(solution.Id == newSolutionInfo.Id && solution.FilePath == newSolutionInfo.FilePath); @@ -82,31 +83,50 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca if (oldSolutionChecksums.AnalyzerReferences.Checksum != newSolutionChecksums.AnalyzerReferences.Checksum) { - solution = solution.WithAnalyzerReferences(await _assetProvider.CreateCollectionAsync( - assetHint: AssetHint.None, newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); + solution = solution.WithAnalyzerReferences(await _assetProvider.GetAssetsArrayAsync( + AssetPathKind.SolutionAnalyzerReferences, newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } if (newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.HasValue && - newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.HasValue) + newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.HasValue && + !newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes.IsDefault) { - var count = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value.Count; - var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity, SourceText)>.GetInstance(count, out var frozenDocuments); + var newSolutionFrozenSourceGeneratedDocumentIdentities = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value; + var newSolutionFrozenSourceGeneratedDocuments = newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value; + var count = newSolutionFrozenSourceGeneratedDocuments.Ids.Length; + var frozenDocuments = new FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>(count); for (var i = 0; i < count; i++) { - var identity = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value[i], cancellationToken).ConfigureAwait(false); + var frozenDocumentId = newSolutionFrozenSourceGeneratedDocuments.Ids[i]; + var frozenDocumentTextChecksum = newSolutionFrozenSourceGeneratedDocuments.TextChecksums[i]; + var frozenDocumentIdentity = newSolutionFrozenSourceGeneratedDocumentIdentities[i]; - var documentStateChecksums = await _assetProvider.GetAssetAsync( - assetHint: AssetHint.None, newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Checksums[i], cancellationToken).ConfigureAwait(false); + var identity = await _assetProvider.GetAssetAsync( + new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentIdentities, frozenDocumentId), frozenDocumentIdentity, cancellationToken).ConfigureAwait(false); - var serializableSourceText = await _assetProvider.GetAssetAsync(assetHint: newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Ids[i], documentStateChecksums.Text, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await _assetProvider.GetAssetAsync( + new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentText, frozenDocumentId), frozenDocumentTextChecksum, cancellationToken).ConfigureAwait(false); + var generationDateTime = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes[i]; var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); - frozenDocuments.Add((identity, text)); + frozenDocuments.Add((identity, generationDateTime, text)); } - solution = solution.WithFrozenSourceGeneratedDocuments(frozenDocuments.ToImmutable()); + solution = solution.WithFrozenSourceGeneratedDocuments(frozenDocuments.MoveToImmutable()); + } + + if (oldSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap != + newSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap) + { + var newVersions = await _assetProvider.GetAssetAsync( + AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, newSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + + // The execution version map will be for the entire solution on the host side. However, we may + // only be syncing over a partial cone. In that case, filter down the version map we apply to + // the local solution to only be for that cone as well. + newVersions = FilterToProjectCone(newVersions, newSolutionChecksums.ProjectCone); + solution = solution.WithSourceGeneratorExecutionVersions(newVersions, cancellationToken); } #if DEBUG @@ -120,6 +140,21 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca { throw ExceptionUtilities.Unreachable(); } + + static SourceGeneratorExecutionVersionMap FilterToProjectCone(SourceGeneratorExecutionVersionMap map, ProjectCone? projectCone) + { + if (projectCone is null) + return map; + + var builder = map.Map.ToBuilder(); + foreach (var (projectId, _) in map.Map) + { + if (!projectCone.Contains(projectId)) + builder.Remove(projectId); + } + + return new(builder.ToImmutable()); + } } private async Task UpdateProjectsAsync( @@ -129,7 +164,6 @@ private async Task UpdateProjectsAsync( using var _1 = PooledDictionary.GetInstance(out var oldProjectIdToChecksum); using var _2 = PooledDictionary.GetInstance(out var newProjectIdToChecksum); - using var _3 = PooledHashSet.GetInstance(out var allProjectIds); foreach (var (oldChecksum, projectId) in oldSolutionChecksums.Projects) oldProjectIdToChecksum.Add(projectId, oldChecksum); @@ -137,8 +171,17 @@ private async Task UpdateProjectsAsync( foreach (var (newChecksum, projectId) in newSolutionChecksums.Projects) newProjectIdToChecksum.Add(projectId, newChecksum); - allProjectIds.AddRange(oldSolutionChecksums.Projects.Ids); - allProjectIds.AddRange(newSolutionChecksums.Projects.Ids); + // remove projects that are the same on both sides. We can just iterate over one of the maps as, + // definitionally, for the project to be on both sides, it will be contained in both. + foreach (var (oldChecksum, projectId) in oldSolutionChecksums.Projects) + { + if (newProjectIdToChecksum.TryGetValue(projectId, out var newChecksum) && + oldChecksum == newChecksum) + { + oldProjectIdToChecksum.Remove(projectId); + newProjectIdToChecksum.Remove(projectId); + } + } // If there are old projects that are now missing on the new side, and this is a projectConeSync, then // exclude them from the old side as well. This way we only consider projects actually added or @@ -161,46 +204,34 @@ private async Task UpdateProjectsAsync( Contract.ThrowIfFalse(oldProjectIdToChecksum.Keys.All(newProjectIdToChecksum.Keys.Contains)); } - // remove projects that are the same on both sides. - foreach (var projectId in allProjectIds) - { - if (oldProjectIdToChecksum.TryGetValue(projectId, out var oldChecksum) && - newProjectIdToChecksum.TryGetValue(projectId, out var newChecksum) && - oldChecksum == newChecksum) - { - oldProjectIdToChecksum.Remove(projectId); - newProjectIdToChecksum.Remove(projectId); - } - } - - using var _4 = PooledDictionary.GetInstance(out var oldProjectIdToStateChecksums); - using var _5 = PooledDictionary.GetInstance(out var newProjectIdToStateChecksums); + using var _3 = PooledDictionary.GetInstance(out var oldProjectIdToStateChecksums); + using var _4 = PooledDictionary.GetInstance(out var newProjectIdToStateChecksums); + // Now, find the full state checksums for all the old projects foreach (var (projectId, oldChecksum) in oldProjectIdToChecksum) { - var oldProjectState = solutionState.GetRequiredProjectState(projectId); - // this should be cheap since we already computed oldSolutionChecksums (which calls into this). - var oldProjectStateChecksums = await oldProjectState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + var oldProjectStateChecksums = await solutionState + .GetRequiredProjectState(projectId) + .GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(oldProjectStateChecksums.ProjectId != projectId); Contract.ThrowIfTrue(oldChecksum != oldProjectStateChecksums.Checksum); oldProjectIdToStateChecksums.Add(projectId, oldProjectStateChecksums); } - // sync over the *info* about all the added/changed projects. We'll want the info so we can determine - // what actually changed. - using var _6 = PooledHashSet.GetInstance(out var newChecksumsToSync); + using var _5 = PooledHashSet.GetInstance(out var newChecksumsToSync); newChecksumsToSync.AddRange(newProjectIdToChecksum.Values); - var newProjectStateChecksums = await _assetProvider.GetAssetsAsync( - assetHint: AssetHint.None, newChecksumsToSync, cancellationToken).ConfigureAwait(false); - - foreach (var (checksum, newProjectStateChecksum) in newProjectStateChecksums) - { - Contract.ThrowIfTrue(checksum != newProjectStateChecksum.Checksum); - newProjectIdToStateChecksums.Add(newProjectStateChecksum.ProjectId, newProjectStateChecksum); - } + await _assetProvider.GetAssetHelper().GetAssetsAsync( + assetPath: AssetPathKind.ProjectStateChecksums, newChecksumsToSync, + static (checksum, newProjectStateChecksum, newProjectIdToStateChecksums) => + { + Contract.ThrowIfTrue(checksum != newProjectStateChecksum.Checksum); + newProjectIdToStateChecksums.Add(newProjectStateChecksum.ProjectId, newProjectStateChecksum); + }, + arg: newProjectIdToStateChecksums, + cancellationToken).ConfigureAwait(false); // Now that we've collected the old and new project state checksums, we can actually process them to // determine what to remove, what to add, and what to change. @@ -217,20 +248,53 @@ private async Task UpdateProjectsAsync( Dictionary newProjectIdToStateChecksums, CancellationToken cancellationToken) { + // Note: it's common to need to collect a large set of project-attributes and compilation options. So + // attempt to collect all of those in a single call for each kind instead of a call for each instance + // needed. + { + using var _ = PooledHashSet.GetInstance(out var projectItemChecksums); + foreach (var (_, newProjectChecksums) in newProjectIdToStateChecksums) + projectItemChecksums.Add(newProjectChecksums.Info); + + await _assetProvider.GetAssetsAsync( + assetPath: AssetPathKind.ProjectAttributes, projectItemChecksums, cancellationToken).ConfigureAwait(false); + + projectItemChecksums.Clear(); + foreach (var (_, newProjectChecksums) in newProjectIdToStateChecksums) + projectItemChecksums.Add(newProjectChecksums.CompilationOptions); + + await _assetProvider.GetAssetsAsync( + assetPath: AssetPathKind.ProjectCompilationOptions, projectItemChecksums, cancellationToken).ConfigureAwait(false); + } + + using var _2 = ArrayBuilder.GetInstance(out var projectStateChecksumsToAdd); + // added project foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) { if (!oldProjectIdToStateChecksums.ContainsKey(projectId)) - { - // bulk sync added project assets fully since we'll definitely need that data, and we won't want - // to make tons of intermediary calls for it. + projectStateChecksumsToAdd.Add(newProjectChecksums); + } + + // bulk sync added project assets fully since we'll definitely need that data, and we can fetch more + // efficiently in bulk and in parallel. + await _assetProvider.SynchronizeProjectAssetsAsync(projectStateChecksumsToAdd, cancellationToken).ConfigureAwait(false); - await _assetProvider.SynchronizeProjectAssetsAsync(newProjectChecksums, cancellationToken).ConfigureAwait(false); - var projectInfo = await _assetProvider.CreateProjectInfoAsync(projectId, newProjectChecksums.Checksum, cancellationToken).ConfigureAwait(false); - solution = solution.AddProject(projectInfo); + using var _3 = ArrayBuilder.GetInstance(projectStateChecksumsToAdd.Count, out var projectInfos); + foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) + { + if (!oldProjectIdToStateChecksums.ContainsKey(projectId)) + { + // Now make a ProjectInfo corresponding to the new project checksums. This should be fast due + // to the bulk sync we just performed above. + var projectInfo = await _assetProvider.CreateProjectInfoAsync(newProjectChecksums, cancellationToken).ConfigureAwait(false); + projectInfos.Add(projectInfo); } } + // Add solutions in bulk. Avoiding intermediary forking of it. + solution = solution.AddProjects(projectInfos); + // remove all project references from projects that changed. this ensures exceptions will not occur for // cyclic references during an incremental update. foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) @@ -240,10 +304,12 @@ private async Task UpdateProjectsAsync( if (oldProjectIdToStateChecksums.TryGetValue(projectId, out var oldProjectChecksums) && oldProjectChecksums.ProjectReferences.Checksum != newProjectChecksums.ProjectReferences.Checksum) { - solution = solution.WithProjectReferences(projectId, SpecializedCollections.EmptyEnumerable()); + solution = solution.WithProjectReferences(projectId, projectReferences: []); } } + using var _4 = ArrayBuilder.GetInstance(out var projectsToRemove); + // removed project foreach (var (projectId, _) in oldProjectIdToStateChecksums) { @@ -251,10 +317,13 @@ private async Task UpdateProjectsAsync( { // Should never be removing projects during cone syncing. Contract.ThrowIfTrue(isConeSync); - solution = solution.RemoveProject(projectId); + projectsToRemove.Add(projectId); } } + // Remove solutions in bulk. Avoiding intermediary forking of it. + solution = solution.RemoveProjects(projectsToRemove); + // changed project foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) { @@ -285,44 +354,42 @@ private async Task UpdateProjectAsync(Project project, ProjectStateChe project = project.WithCompilationOptions( project.State.ProjectInfo.Attributes.FixUpCompilationOptions( await _assetProvider.GetAssetAsync( - assetHint: project.Id, newProjectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false))); + assetPath: project.Id, newProjectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false))); } // changed parse options if (oldProjectChecksums.ParseOptions != newProjectChecksums.ParseOptions) { project = project.WithParseOptions(await _assetProvider.GetAssetAsync( - assetHint: project.Id, newProjectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false)); + assetPath: project.Id, newProjectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false)); } // changed project references if (oldProjectChecksums.ProjectReferences.Checksum != newProjectChecksums.ProjectReferences.Checksum) { - project = project.WithProjectReferences(await _assetProvider.CreateCollectionAsync( - assetHint: project.Id, newProjectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false)); + project = project.WithProjectReferences(await _assetProvider.GetAssetsArrayAsync( + assetPath: project.Id, newProjectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false)); } // changed metadata references if (oldProjectChecksums.MetadataReferences.Checksum != newProjectChecksums.MetadataReferences.Checksum) { - project = project.WithMetadataReferences(await _assetProvider.CreateCollectionAsync( - assetHint: project.Id, newProjectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false)); + project = project.WithMetadataReferences(await _assetProvider.GetAssetsArrayAsync( + assetPath: project.Id, newProjectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false)); } // changed analyzer references if (oldProjectChecksums.AnalyzerReferences.Checksum != newProjectChecksums.AnalyzerReferences.Checksum) { - project = project.WithAnalyzerReferences(await _assetProvider.CreateCollectionAsync( - assetHint: project.Id, newProjectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); + project = project.WithAnalyzerReferences(await _assetProvider.GetAssetsArrayAsync( + assetPath: project.Id, newProjectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } // changed analyzer references if (oldProjectChecksums.Documents.Checksum != newProjectChecksums.Documents.Checksum) { - project = await UpdateDocumentsAsync( + project = await UpdateDocumentsAsync( project, - newProjectChecksums, - project.State.DocumentStates, oldProjectChecksums.Documents, newProjectChecksums.Documents, static (solution, documents) => solution.AddDocuments(documents), @@ -333,10 +400,8 @@ await _assetProvider.GetAssetAsync( // changed additional documents if (oldProjectChecksums.AdditionalDocuments.Checksum != newProjectChecksums.AdditionalDocuments.Checksum) { - project = await UpdateDocumentsAsync( + project = await UpdateDocumentsAsync( project, - newProjectChecksums, - project.State.AdditionalDocumentStates, oldProjectChecksums.AdditionalDocuments, newProjectChecksums.AdditionalDocuments, static (solution, documents) => solution.AddAdditionalDocuments(documents), @@ -347,10 +412,8 @@ await _assetProvider.GetAssetAsync( // changed analyzer config documents if (oldProjectChecksums.AnalyzerConfigDocuments.Checksum != newProjectChecksums.AnalyzerConfigDocuments.Checksum) { - project = await UpdateDocumentsAsync( + project = await UpdateDocumentsAsync( project, - newProjectChecksums, - project.State.AnalyzerConfigDocumentStates, oldProjectChecksums.AnalyzerConfigDocuments, newProjectChecksums.AnalyzerConfigDocuments, static (solution, documents) => solution.AddAnalyzerConfigDocuments(documents), @@ -364,7 +427,7 @@ await _assetProvider.GetAssetAsync( private async Task UpdateProjectInfoAsync(Project project, Checksum infoChecksum, CancellationToken cancellationToken) { var newProjectAttributes = await _assetProvider.GetAssetAsync( - assetHint: project.Id, infoChecksum, cancellationToken).ConfigureAwait(false); + assetPath: project.Id, infoChecksum, cancellationToken).ConfigureAwait(false); // there is no API to change these once project is created Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.Id == newProjectAttributes.Id); @@ -375,52 +438,52 @@ private async Task UpdateProjectInfoAsync(Project project, Checksum inf if (project.State.ProjectInfo.Attributes.Name != newProjectAttributes.Name) { - project = project.Solution.WithProjectName(projectId, newProjectAttributes.Name).GetProject(projectId)!; + project = project.Solution.WithProjectName(projectId, newProjectAttributes.Name).GetRequiredProject(projectId); } if (project.State.ProjectInfo.Attributes.AssemblyName != newProjectAttributes.AssemblyName) { - project = project.Solution.WithProjectAssemblyName(projectId, newProjectAttributes.AssemblyName).GetProject(projectId)!; + project = project.Solution.WithProjectAssemblyName(projectId, newProjectAttributes.AssemblyName).GetRequiredProject(projectId); } if (project.State.ProjectInfo.Attributes.FilePath != newProjectAttributes.FilePath) { - project = project.Solution.WithProjectFilePath(projectId, newProjectAttributes.FilePath).GetProject(projectId)!; + project = project.Solution.WithProjectFilePath(projectId, newProjectAttributes.FilePath).GetRequiredProject(projectId); } if (project.State.ProjectInfo.Attributes.OutputFilePath != newProjectAttributes.OutputFilePath) { - project = project.Solution.WithProjectOutputFilePath(projectId, newProjectAttributes.OutputFilePath).GetProject(projectId)!; + project = project.Solution.WithProjectOutputFilePath(projectId, newProjectAttributes.OutputFilePath).GetRequiredProject(projectId); } if (project.State.ProjectInfo.Attributes.OutputRefFilePath != newProjectAttributes.OutputRefFilePath) { - project = project.Solution.WithProjectOutputRefFilePath(projectId, newProjectAttributes.OutputRefFilePath).GetProject(projectId)!; + project = project.Solution.WithProjectOutputRefFilePath(projectId, newProjectAttributes.OutputRefFilePath).GetRequiredProject(projectId); } if (project.State.ProjectInfo.Attributes.CompilationOutputInfo != newProjectAttributes.CompilationOutputInfo) { - project = project.Solution.WithProjectCompilationOutputInfo(project.Id, newProjectAttributes.CompilationOutputInfo).GetProject(project.Id)!; + project = project.Solution.WithProjectCompilationOutputInfo(project.Id, newProjectAttributes.CompilationOutputInfo).GetRequiredProject(project.Id); } if (project.State.ProjectInfo.Attributes.DefaultNamespace != newProjectAttributes.DefaultNamespace) { - project = project.Solution.WithProjectDefaultNamespace(projectId, newProjectAttributes.DefaultNamespace).GetProject(projectId)!; + project = project.Solution.WithProjectDefaultNamespace(projectId, newProjectAttributes.DefaultNamespace).GetRequiredProject(projectId); } if (project.State.ProjectInfo.Attributes.HasAllInformation != newProjectAttributes.HasAllInformation) { - project = project.Solution.WithHasAllInformation(projectId, newProjectAttributes.HasAllInformation).GetProject(projectId)!; + project = project.Solution.WithHasAllInformation(projectId, newProjectAttributes.HasAllInformation).GetRequiredProject(projectId); } if (project.State.ProjectInfo.Attributes.RunAnalyzers != newProjectAttributes.RunAnalyzers) { - project = project.Solution.WithRunAnalyzers(projectId, newProjectAttributes.RunAnalyzers).GetProject(projectId)!; + project = project.Solution.WithRunAnalyzers(projectId, newProjectAttributes.RunAnalyzers).GetRequiredProject(projectId); } if (project.State.ProjectInfo.Attributes.ChecksumAlgorithm != newProjectAttributes.ChecksumAlgorithm) { - project = project.Solution.WithProjectChecksumAlgorithm(projectId, newProjectAttributes.ChecksumAlgorithm).GetProject(projectId)!; + project = project.Solution.WithProjectChecksumAlgorithm(projectId, newProjectAttributes.ChecksumAlgorithm).GetRequiredProject(projectId); } return project; @@ -428,38 +491,59 @@ private async Task UpdateProjectInfoAsync(Project project, Checksum inf private async Task UpdateDocumentsAsync( Project project, - ProjectStateChecksums projectChecksums, - TextDocumentStates existingTextDocumentStates, - ChecksumsAndIds oldChecksums, - ChecksumsAndIds newChecksums, + DocumentChecksumsAndIds oldChecksums, + DocumentChecksumsAndIds newChecksums, Func, Solution> addDocuments, Func, Solution> removeDocuments, CancellationToken cancellationToken) where TDocumentState : TextDocumentState { - using var _1 = PooledHashSet.GetInstance(out var olds); - using var _2 = PooledHashSet.GetInstance(out var news); + using var _1 = PooledDictionary.GetInstance(out var oldDocumentIdToChecksums); + using var _2 = PooledDictionary.GetInstance(out var newDocumentIdToChecksums); - olds.AddRange(oldChecksums.Checksums.Children); - news.AddRange(newChecksums.Checksums.Children); + foreach (var (oldAttributeChecksum, oldTextChecksum, documentId) in oldChecksums) + oldDocumentIdToChecksums.Add(documentId, (oldAttributeChecksum, oldTextChecksum)); - // remove documents that exist in both side - olds.ExceptWith(newChecksums.Checksums); - news.ExceptWith(oldChecksums.Checksums); + foreach (var (newAttributeChecksum, newTextChecksum, documentId) in newChecksums) + newDocumentIdToChecksums.Add(documentId, (newAttributeChecksum, newTextChecksum)); - using var _3 = PooledDictionary.GetInstance(out var oldDocumentIdToStateChecksums); - using var _4 = PooledDictionary.GetInstance(out var newDocumentIdToStateChecksums); - - await PopulateOldDocumentMapAsync().ConfigureAwait(false); - await PopulateNewDocumentMapAsync(this).ConfigureAwait(false); - - // If more than two documents changed during a single update, perform a bulk synchronization on the - // project to avoid large numbers of small synchronization calls during document updates. - // 🔗 https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1365014 - if (newDocumentIdToStateChecksums.Count > 2) + // remove documents that are the same on both sides. We can just iterate over one of the maps as, + // definitionally, for the project to be on both sides, it will be contained in both. + foreach (var (oldAttributeChecksum, oldTextChecksum, documentId) in oldChecksums) { - await _assetProvider.SynchronizeProjectAssetsAsync(projectChecksums, cancellationToken).ConfigureAwait(false); + if (newDocumentIdToChecksums.TryGetValue(documentId, out var newChecksum) && + oldAttributeChecksum == newChecksum.attributeChecksum && + oldTextChecksum == newChecksum.textChecksum) + { + oldDocumentIdToChecksums.Remove(documentId); + newDocumentIdToChecksums.Remove(documentId); + } } + // sync over the *info* about all the added/changed documents. We'll want the info so we can determine + // what actually changed. + using var _5 = PooledHashSet.GetInstance(out var newChecksumsToSync); + newChecksumsToSync.AddRange(newDocumentIdToChecksums.Values.Select(v => v.attributeChecksum)); + + await _assetProvider.GetAssetsAsync( + assetPath: new(AssetPathKind.DocumentAttributes, project.Id), newChecksumsToSync, cancellationToken).ConfigureAwait(false); + + newChecksumsToSync.Clear(); + newChecksumsToSync.AddRange(newDocumentIdToChecksums.Values.Select(v => v.textChecksum)); + + await _assetProvider.GetAssetsAsync( + assetPath: new(AssetPathKind.DocumentText, project.Id), newChecksumsToSync, cancellationToken).ConfigureAwait(false); + + return await UpdateDocumentsAsync(project, addDocuments, removeDocuments, oldDocumentIdToChecksums, newDocumentIdToChecksums, cancellationToken).ConfigureAwait(false); + } + + private async Task UpdateDocumentsAsync( + Project project, + Func, Solution> addDocuments, + Func, Solution> removeDocuments, + Dictionary oldDocumentIdToStateChecksums, + Dictionary newDocumentIdToStateChecksums, + CancellationToken cancellationToken) + { // added document ImmutableArray.Builder? lazyDocumentsToAdd = null; foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) @@ -470,7 +554,7 @@ private async Task UpdateDocumentsAsync( // we have new document added var documentInfo = await _assetProvider.CreateDocumentInfoAsync( - documentId, newDocumentChecksums.Checksum, cancellationToken).ConfigureAwait(false); + documentId, newDocumentChecksums.attributeChecksum, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); lazyDocumentsToAdd.Add(documentInfo); } } @@ -480,22 +564,6 @@ private async Task UpdateDocumentsAsync( project = addDocuments(project.Solution, lazyDocumentsToAdd.ToImmutable()).GetProject(project.Id)!; } - // changed document - foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) - { - if (!oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums)) - { - continue; - } - - Contract.ThrowIfTrue(oldDocumentChecksums.Checksum == newDocumentChecksums.Checksum); - - var document = project.GetDocument(documentId) ?? project.GetAdditionalDocument(documentId) ?? project.GetAnalyzerConfigDocument(documentId); - Contract.ThrowIfNull(document); - - project = await UpdateDocumentAsync(document, oldDocumentChecksums, newDocumentChecksums, cancellationToken).ConfigureAwait(false); - } - // removed document ImmutableArray.Builder? lazyDocumentsToRemove = null; foreach (var (documentId, _) in oldDocumentIdToStateChecksums) @@ -513,48 +581,50 @@ private async Task UpdateDocumentsAsync( project = removeDocuments(project.Solution, lazyDocumentsToRemove.ToImmutable()).GetProject(project.Id)!; } - return project; - - async Task PopulateOldDocumentMapAsync() + // changed document + foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) { - foreach (var (_, state) in existingTextDocumentStates.States) - { - var documentChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - if (olds.Contains(documentChecksums.Checksum)) - oldDocumentIdToStateChecksums.Add(state.Id, documentChecksums); - } - } + if (!oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums)) + continue; - async Task PopulateNewDocumentMapAsync(SolutionCreator @this) - { - var documentStateChecksums = await @this._assetProvider.GetAssetsAsync( - assetHint: project.Id, news, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfTrue( + oldDocumentChecksums.attributeChecksum == newDocumentChecksums.attributeChecksum && + oldDocumentChecksums.textChecksum == newDocumentChecksums.textChecksum); - foreach (var (_, documentStateChecksum) in documentStateChecksums) - newDocumentIdToStateChecksums.Add(documentStateChecksum.DocumentId, documentStateChecksum); + var document = project.GetDocument(documentId) ?? project.GetAdditionalDocument(documentId) ?? project.GetAnalyzerConfigDocument(documentId); + Contract.ThrowIfNull(document); + + project = await UpdateDocumentAsync(document, oldDocumentChecksums, newDocumentChecksums, cancellationToken).ConfigureAwait(false); } + + return project; } - private async Task UpdateDocumentAsync(TextDocument document, DocumentStateChecksums oldDocumentChecksums, DocumentStateChecksums newDocumentChecksums, CancellationToken cancellationToken) + private async Task UpdateDocumentAsync( + TextDocument document, + (Checksum attributeChecksum, Checksum textChecksum) oldDocumentChecksums, + (Checksum attributeChecksum, Checksum textChecksum) newDocumentChecksums, + CancellationToken cancellationToken) { // changed info - if (oldDocumentChecksums.Info != newDocumentChecksums.Info) + if (oldDocumentChecksums.attributeChecksum != newDocumentChecksums.attributeChecksum) { - document = await UpdateDocumentInfoAsync(document, newDocumentChecksums.Info, cancellationToken).ConfigureAwait(false); + document = await UpdateDocumentInfoAsync(document, newDocumentChecksums.attributeChecksum, cancellationToken).ConfigureAwait(false); } // changed text - if (oldDocumentChecksums.Text != newDocumentChecksums.Text) + if (oldDocumentChecksums.textChecksum != newDocumentChecksums.textChecksum) { var serializableSourceText = await _assetProvider.GetAssetAsync( - assetHint: document.Id, newDocumentChecksums.Text, cancellationToken).ConfigureAwait(false); - var sourceText = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); + assetPath: document.Id, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); + var loader = serializableSourceText.ToTextLoader(document.FilePath); + var mode = PreservationMode.PreserveValue; document = document.Kind switch { - TextDocumentKind.Document => document.Project.Solution.WithDocumentText(document.Id, sourceText).GetDocument(document.Id)!, - TextDocumentKind.AnalyzerConfigDocument => document.Project.Solution.WithAnalyzerConfigDocumentText(document.Id, sourceText).GetAnalyzerConfigDocument(document.Id)!, - TextDocumentKind.AdditionalDocument => document.Project.Solution.WithAdditionalDocumentText(document.Id, sourceText).GetAdditionalDocument(document.Id)!, + TextDocumentKind.Document => document.Project.Solution.WithDocumentTextLoader(document.Id, loader, mode).GetRequiredDocument(document.Id), + TextDocumentKind.AnalyzerConfigDocument => document.Project.Solution.WithAnalyzerConfigDocumentTextLoader(document.Id, loader, mode).GetRequiredAnalyzerConfigDocument(document.Id), + TextDocumentKind.AdditionalDocument => document.Project.Solution.WithAdditionalDocumentTextLoader(document.Id, loader, mode).GetRequiredAdditionalDocument(document.Id), _ => throw ExceptionUtilities.UnexpectedValue(document.Kind), }; } @@ -565,7 +635,7 @@ private async Task UpdateDocumentAsync(TextDocument document, DocumentS private async Task UpdateDocumentInfoAsync(TextDocument document, Checksum infoChecksum, CancellationToken cancellationToken) { var newDocumentInfo = await _assetProvider.GetAssetAsync( - assetHint: document.Id, infoChecksum, cancellationToken).ConfigureAwait(false); + assetPath: document.Id, infoChecksum, cancellationToken).ConfigureAwait(false); // there is no api to change these once document is created Contract.ThrowIfFalse(document.State.Attributes.Id == newDocumentInfo.Id); @@ -615,7 +685,7 @@ private async Task ValidateChecksumAsync( var workspace = new AdhocWorkspace(_hostServices); workspace.AddSolution(solutionInfo); - await TestUtils.AssertChecksumsAsync(_assetProvider, checksumFromRequest, workspace.CurrentSolution, incrementalSolutionBuilt).ConfigureAwait(false); + await TestUtils.AssertChecksumsAsync(_assetProvider, checksumFromRequest, workspace.CurrentSolution, incrementalSolutionBuilt, projectConeId).ConfigureAwait(false); } #endif } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index 0471dfcde019e..6971bab2213f0 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; @@ -25,12 +26,6 @@ internal sealed partial class RemoteWorkspace : Workspace /// private readonly SemaphoreSlim _gate = new(initialCount: 1); - /// - /// Used to make sure we never move remote workspace backward. this version is the WorkspaceVersion of primary - /// solution in client (VS) we are currently caching. - /// - private int _currentRemoteWorkspaceVersion = -1; - // internal for testing purposes. internal RemoteWorkspace(HostServices hostServices) : base(hostServices, WorkspaceKind.RemoteWorkspace) @@ -52,7 +47,7 @@ public AssetProvider CreateAssetProvider(Checksum solutionChecksum, SolutionAsse /// them to be pre-populated for feature requests that come in soon after this call completes. /// public async Task UpdatePrimaryBranchSolutionAsync( - AssetProvider assetProvider, Checksum solutionChecksum, int workspaceVersion, CancellationToken cancellationToken) + AssetProvider assetProvider, Checksum solutionChecksum, CancellationToken cancellationToken) { // See if the current snapshot we're pointing at is the same one the host wants us to sync to. If so, we // don't need to do anything. @@ -65,7 +60,6 @@ public async Task UpdatePrimaryBranchSolutionAsync( await RunWithSolutionAsync( assetProvider, solutionChecksum, - workspaceVersion, updatePrimaryBranch: true, implementation: static _ => ValueTaskFactory.FromResult(false), cancellationToken).ConfigureAwait(false); @@ -90,13 +84,12 @@ await RunWithSolutionAsync( Func> implementation, CancellationToken cancellationToken) { - return RunWithSolutionAsync(assetProvider, solutionChecksum, workspaceVersion: -1, updatePrimaryBranch: false, implementation, cancellationToken); + return RunWithSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, implementation, cancellationToken); } private async ValueTask<(Solution solution, T result)> RunWithSolutionAsync( AssetProvider assetProvider, Checksum solutionChecksum, - int workspaceVersion, bool updatePrimaryBranch, Func> implementation, CancellationToken cancellationToken) @@ -131,7 +124,7 @@ await RunWithSolutionAsync( try { inFlightSolution = GetOrCreateSolutionAndAddInFlightCount_NoLock( - assetProvider, solutionChecksum, workspaceVersion, updatePrimaryBranch); + assetProvider, solutionChecksum, updatePrimaryBranch); solutionTask = inFlightSolution.PreferredSolutionTask_NoLock; // We must have at least 1 for the in-flight-count (representing this current in-flight call). @@ -262,40 +255,23 @@ private async Task ComputeDisconnectedSolutionAsync( private Solution CreateSolutionFromInfo(SolutionInfo solutionInfo) { var solution = this.CreateSolution(solutionInfo); - foreach (var projectInfo in solutionInfo.Projects) - solution = solution.AddProject(projectInfo); - return solution; + using var _ = ArrayBuilder.GetInstance(solutionInfo.Projects.Count, out var projectInfos); + projectInfos.AddRange(solutionInfo.Projects); + + // Add in one operation, avoiding intermediary forking of the solution. + return solution.AddProjects(projectInfos); } /// - /// Attempts to update this workspace with the given . If this succeeds, will be returned in the tuple result as well as the actual solution that the workspace is - /// updated to point at. If we cannot update this workspace, then will be returned, - /// along with the solution passed in. The only time the solution can not be updated is if it would move backwards. + /// Updates this workspace with the given . The solution returned is the actual + /// one the workspace now points to. /// - private async Task TryUpdateWorkspaceCurrentSolutionAsync( - int workspaceVersion, - Solution newSolution, - CancellationToken cancellationToken) - { - var (solution, _) = await TryUpdateWorkspaceCurrentSolutionWorkerAsync(workspaceVersion, newSolution, cancellationToken).ConfigureAwait(false); - return solution; - } - - private async ValueTask<(Solution solution, bool updated)> TryUpdateWorkspaceCurrentSolutionWorkerAsync( - int workspaceVersion, + private async Task UpdateWorkspaceCurrentSolutionAsync( Solution newSolution, CancellationToken cancellationToken) { using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - // Never move workspace backward - if (workspaceVersion <= _currentRemoteWorkspaceVersion) - return (newSolution, updated: false); - - _currentRemoteWorkspaceVersion = workspaceVersion; - // if either solution id or file path changed, then we consider it as new solution. Otherwise, // update the current solution in place. @@ -316,7 +292,7 @@ private async Task TryUpdateWorkspaceCurrentSolutionAsync( } }); - return (newSolution, updated: true); + return newSolution; } static bool IsAddingSolution(Solution oldSolution, Solution newSolution) @@ -338,18 +314,17 @@ public TestAccessor(RemoteWorkspace remoteWorkspace) public Solution CreateSolutionFromInfo(SolutionInfo solutionInfo) => _remoteWorkspace.CreateSolutionFromInfo(solutionInfo); - public ValueTask<(Solution solution, bool updated)> TryUpdateWorkspaceCurrentSolutionAsync(Solution newSolution, int workspaceVersion) - => _remoteWorkspace.TryUpdateWorkspaceCurrentSolutionWorkerAsync(workspaceVersion, newSolution, CancellationToken.None); + public Task UpdateWorkspaceCurrentSolutionAsync(Solution newSolution) + => _remoteWorkspace.UpdateWorkspaceCurrentSolutionAsync(newSolution, CancellationToken.None); public async ValueTask GetSolutionAsync( AssetProvider assetProvider, Checksum solutionChecksum, bool updatePrimaryBranch, - int workspaceVersion, CancellationToken cancellationToken) { var (solution, _) = await _remoteWorkspace.RunWithSolutionAsync( - assetProvider, solutionChecksum, workspaceVersion, updatePrimaryBranch, _ => ValueTaskFactory.FromResult(false), cancellationToken).ConfigureAwait(false); + assetProvider, solutionChecksum, updatePrimaryBranch, _ => ValueTaskFactory.FromResult(false), cancellationToken).ConfigureAwait(false); return solution; } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs index 61916c58da957..b6a17c45389b9 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs @@ -37,11 +37,11 @@ internal class RemoteWorkspaceManager /// allowing it to get too full. /// /// Also note that the asset cache will not remove items associated with the of the workspace it is created against. This ensures that the assets - /// associated with the solution that most closely corresponds to what the user is working with will stay pinned - /// on the remote side and not get purged just because the user stopped interactive for a while. This ensures - /// the next sync (which likely overlaps heavily with the current solution) will not force the same assets to be - /// resent. + /// cref="Workspace.CurrentSolution"/> of the workspace it is created against (as well as any recent in-flight + /// solutions). This ensures that the assets associated with the solution that most closely corresponds to what + /// the user is working with will stay pinned on the remote side and not get purged just because the user + /// stopped interactive for a while. This ensures the next sync (which likely overlaps heavily with the current + /// solution) will not force the same assets to be resent. /// /// /// CleanupInterval=30s gives what feels to be a reasonable non-aggressive amount of time to let the cache diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs index d6c82aee29f98..119d58327f58f 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs @@ -4,12 +4,13 @@ using System; using System.Collections.Generic; -using Microsoft.CodeAnalysis.Internal.Log; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote { - internal sealed partial class RemoteWorkspace { /// @@ -40,7 +41,6 @@ internal sealed partial class RemoteWorkspace private InFlightSolution GetOrCreateSolutionAndAddInFlightCount_NoLock( AssetProvider assetProvider, Checksum solutionChecksum, - int workspaceVersion, bool updatePrimaryBranch) { Contract.ThrowIfFalse(_gate.CurrentCount == 0); @@ -57,7 +57,7 @@ private InFlightSolution GetOrCreateSolutionAndAddInFlightCount_NoLock( if (updatePrimaryBranch) { solution.TryKickOffPrimaryBranchWork_NoLock((disconnectedSolution, cancellationToken) => - this.TryUpdateWorkspaceCurrentSolutionAsync(workspaceVersion, disconnectedSolution, cancellationToken)); + this.UpdateWorkspaceCurrentSolutionAsync(disconnectedSolution, cancellationToken)); } CheckCacheInvariants_NoLock(); @@ -114,5 +114,28 @@ private void CheckCacheInvariants_NoLock() Contract.ThrowIfTrue(solutionChecksum != solution.SolutionChecksum); } } + + /// + /// Gets all the solution instances this remote workspace knows about because of the primary solution or any + /// in-flight operations. + /// + public async ValueTask AddPinnedSolutionsAsync(HashSet solutions, CancellationToken cancellationToken) + { + using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + // Ensure everything in the workspace's current solution is pinned. We def don't want any of its data + // dropped from the checksum->asset cache. + solutions.Add(this.CurrentSolution); + + // Also the data for the last 'current solution' this workspace had that we actually got an OOP request + // for. this is commonly the same as CurrentSolution, but technically could be slightly behind if the + // primary solution just got updated. + solutions.AddIfNotNull(_lastRequestedPrimaryBranchSolution.solution); + + // Also add the last few forked solutions we were asked about. As with the above solutions, there's a + // reasonable chance it will refer to data needed by future oop calls. + _lastRequestedAnyBranchSolutions.AddAllTo(solutions); + } + } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs index c167b7162380b..b409b3b8e28db 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs @@ -11,231 +11,270 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Remote +namespace Microsoft.CodeAnalysis.Remote; + +internal sealed class SolutionAssetCache { - internal sealed class SolutionAssetCache + static SolutionAssetCache() { - static SolutionAssetCache() - { - // CRITICAL: The size SharedStopwatch is the size of a TimeSpan (which itself is the size of a long). This - // allows stopwatches to be atomically overwritten, without a concern for torn writes, as long as we're - // running on 64bit machines. Make sure this value doesn't change as that will cause these current - // consumers to be invalid. - RoslynDebug.Assert(Marshal.SizeOf(typeof(SharedStopwatch)) == 8); - } + // CRITICAL: The size SharedStopwatch is the size of a TimeSpan (which itself is the size of a long). This + // allows stopwatches to be atomically overwritten, without a concern for torn writes, as long as we're + // running on 64bit machines. Make sure this value doesn't change as that will cause these current + // consumers to be invalid. + RoslynDebug.Assert(Marshal.SizeOf(typeof(SharedStopwatch)) == 8); + } + + /// + /// Workspace we are associated with. When we purge items from teh cache, we will avoid any items associated + /// with the items in its 'CurrentSolution'. + /// + private readonly RemoteWorkspace? _remoteWorkspace; + + /// + /// Time interval we check storage for cleanup + /// + private readonly TimeSpan _cleanupIntervalTimeSpan; + + /// + /// Time span data can sit inside of cache () without being used. + /// after that, it will be removed from the cache. + /// + private readonly TimeSpan _purgeAfterTimeSpan; + + /// + /// Time we will wait after the last activity before doing explicit GC cleanup. + /// We monitor all resource access and service call to track last activity time. + /// + /// We do this since 64bit process can hold onto quite big unused memory when + /// OOP is running as AnyCpu + /// + private readonly TimeSpan _gcAfterTimeSpan; + + private readonly ConcurrentDictionary _assets = new(); + + private DateTime _lastGCRun; + private DateTime _lastActivityTime; + + // constructor for testing + public SolutionAssetCache() + { + } + + /// + /// Create central data cache + /// + /// time interval to clean up + /// time unused data can sit in the cache + /// time we wait before it call GC since last activity + public SolutionAssetCache(RemoteWorkspace? remoteWorkspace, TimeSpan cleanupInterval, TimeSpan purgeAfter, TimeSpan gcAfter) + { + _remoteWorkspace = remoteWorkspace; + _cleanupIntervalTimeSpan = cleanupInterval; + _purgeAfterTimeSpan = purgeAfter; + _gcAfterTimeSpan = gcAfter; + + _lastActivityTime = DateTime.UtcNow; + _lastGCRun = DateTime.UtcNow; + + Task.Run(CleanAssetsAsync, CancellationToken.None); + } + + public object GetOrAdd(Checksum checksum, object value) + { + Contract.ThrowIfNull(value); + UpdateLastActivityTime(); + + var entry = _assets.GetOrAdd(checksum, new Entry(value)); + Update(entry); + return entry.Object; + } + + public bool TryGetAsset(Checksum checksum, [MaybeNullWhen(false)] out T value) + { + UpdateLastActivityTime(); - /// - /// Workspace we are associated with. When we purge items from teh cache, we will avoid any items associated - /// with the items in its 'CurrentSolution'. - /// - private readonly RemoteWorkspace? _remoteWorkspace; - - /// - /// Time interval we check storage for cleanup - /// - private readonly TimeSpan _cleanupIntervalTimeSpan; - - /// - /// Time span data can sit inside of cache () without being used. - /// after that, it will be removed from the cache. - /// - private readonly TimeSpan _purgeAfterTimeSpan; - - /// - /// Time we will wait after the last activity before doing explicit GC cleanup. - /// We monitor all resource access and service call to track last activity time. - /// - /// We do this since 64bit process can hold onto quite big unused memory when - /// OOP is running as AnyCpu - /// - private readonly TimeSpan _gcAfterTimeSpan; - - private readonly ConcurrentDictionary _assets = new(concurrencyLevel: 4, capacity: 10); - - private DateTime _lastGCRun; - private DateTime _lastActivityTime; - - // constructor for testing - public SolutionAssetCache() + using (Logger.LogBlock(FunctionId.AssetStorage_TryGetAsset, Checksum.GetChecksumLogInfo, checksum, CancellationToken.None)) { + if (!_assets.TryGetValue(checksum, out var entry)) + { + value = default; + return false; + } + + // Update timestamp + Update(entry); + + value = (T)entry.Object; + return true; } + } + + public bool ContainsAsset(Checksum checksum) + => _assets.ContainsKey(checksum); - /// - /// Create central data cache - /// - /// time interval to clean up - /// time unused data can sit in the cache - /// time we wait before it call GC since last activity - public SolutionAssetCache(RemoteWorkspace? remoteWorkspace, TimeSpan cleanupInterval, TimeSpan purgeAfter, TimeSpan gcAfter) + public void UpdateLastActivityTime() + => _lastActivityTime = DateTime.UtcNow; + + private static void Update(Entry entry) + { + // Stopwatch wraps a TimeSpan (which is only 64bits) (asserted in our shared constructor). so this + // assignment can be done safely without a concern for torn writes on 64 systems. + // + // Note: on 32 bit systems there could be an issue here both with a torn write/read or torn write/write. We + // think that's probably ok as a torn read only leads to suboptimal behavior (dropping something early, or + // keeping something around till the next purge), and a torn write should likely still lead to reasonable + // data being written as both writers will likely still write something reasonable once both writes go + // through. e.g. if you have a writer writing 1234-5678 and one writing 1235-0000, then getting 1235-5678 + // or 1234-0000 is still fine as a final outcome. + entry.Stopwatch = SharedStopwatch.StartNew(); + } + + private async Task CleanAssetsAsync() + { + // Todo: associate this with a real CancellationToken that can shutdown this work. + var cancellationToken = CancellationToken.None; + while (!cancellationToken.IsCancellationRequested) { - _remoteWorkspace = remoteWorkspace; - _cleanupIntervalTimeSpan = cleanupInterval; - _purgeAfterTimeSpan = purgeAfter; - _gcAfterTimeSpan = gcAfter; + await CleanAssetsWorkerAsync(cancellationToken).ConfigureAwait(false); - _lastActivityTime = DateTime.UtcNow; - _lastGCRun = DateTime.UtcNow; + ForceGC(); - Task.Run(CleanAssetsAsync, CancellationToken.None); + await Task.Delay(_cleanupIntervalTimeSpan, cancellationToken).ConfigureAwait(false); } + } - public object GetOrAdd(Checksum checksum, object value) + private void ForceGC() + { + // if there was no activity since last GC run. we don't have anything to do + if (_lastGCRun >= _lastActivityTime) { - UpdateLastActivityTime(); - - var entry = _assets.GetOrAdd(checksum, new Entry(value)); - Update(entry); - return entry.Object; + return; } - public bool TryGetAsset(Checksum checksum, [MaybeNullWhen(false)] out T value) + var current = DateTime.UtcNow; + if (current - _lastActivityTime < _gcAfterTimeSpan) { - UpdateLastActivityTime(); + // we are having activities. + return; + } - using (Logger.LogBlock(FunctionId.AssetStorage_TryGetAsset, Checksum.GetChecksumLogInfo, checksum, CancellationToken.None)) + using (Logger.LogBlock(FunctionId.AssetStorage_ForceGC, CancellationToken.None)) + { + // we didn't have activity for 5 min. spend some time to drop + // unused memory + for (var i = 0; i < 3; i++) { - if (!_assets.TryGetValue(checksum, out var entry)) - { - value = default; - return false; - } - - // Update timestamp - Update(entry); - - value = (T)entry.Object; - return true; + GC.Collect(); } } - public bool ContainsAsset(Checksum checksum) - => _assets.ContainsKey(checksum); - - public void UpdateLastActivityTime() - => _lastActivityTime = DateTime.UtcNow; + // update gc run time + _lastGCRun = current; + } - private static void Update(Entry entry) + private async ValueTask CleanAssetsWorkerAsync(CancellationToken cancellationToken) + { + if (_assets.IsEmpty) { - // Stopwatch wraps a TimeSpan (which is only 64bits) (asserted in our shared constructor). so this - // assignment can be done safely without a concern for torn writes on 64 systems. - // - // Note: on 32 bit systems there could be an issue here both with a torn write/read or torn write/write. We - // think that's probably ok as a torn read only leads to suboptimal behavior (dropping something early, or - // keeping something around till the next purge), and a torn write should likely still lead to reasonable - // data being written as both writers will likely still write something reasonable once both writes go - // through. e.g. if you have a writer writing 1234-5678 and one writing 1235-0000, then getting 1235-5678 - // or 1234-0000 is still fine as a final outcome. - entry.Stopwatch = SharedStopwatch.StartNew(); + // no asset, nothing to do. + return; } - private async Task CleanAssetsAsync() + using (Logger.LogBlock(FunctionId.AssetStorage_CleanAssets, cancellationToken)) { - // Todo: associate this with a real CancellationToken that can shutdown this work. - var cancellationToken = CancellationToken.None; - while (!cancellationToken.IsCancellationRequested) + // Ensure that if our remote workspace has a current solution, that we don't purge any items associated + // with that solution. + using var _1 = PooledHashSet.GetInstance(out var pinnedChecksums); + + foreach (var (checksum, entry) in _assets) { - await CleanAssetsWorkerAsync(cancellationToken).ConfigureAwait(false); + // If not enough time has passed, keep in the cache. + if (entry.Stopwatch.Elapsed <= _purgeAfterTimeSpan) + continue; - ForceGC(); + // If this is a checksum we want to pin, do not remove it. + if (pinnedChecksums.Count == 0) + await AddPinnedChecksumsAsync(pinnedChecksums, cancellationToken).ConfigureAwait(false); - await Task.Delay(_cleanupIntervalTimeSpan, cancellationToken).ConfigureAwait(false); + if (pinnedChecksums.Contains(checksum)) + continue; + + _assets.TryRemove(checksum, out _); } } + } - private void ForceGC() - { - // if there was no activity since last GC run. we don't have anything to do - if (_lastGCRun >= _lastActivityTime) - { - return; - } + private async ValueTask AddPinnedChecksumsAsync(HashSet pinnedChecksums, CancellationToken cancellationToken) + { + if (_remoteWorkspace is null) + return; - var current = DateTime.UtcNow; - if (current - _lastActivityTime < _gcAfterTimeSpan) - { - // we are having activities. - return; - } + using var _1 = PooledHashSet.GetInstance(out var pinnedSolutions); - using (Logger.LogBlock(FunctionId.AssetStorage_ForceGC, CancellationToken.None)) - { - // we didn't have activity for 5 min. spend some time to drop - // unused memory - for (var i = 0; i < 3; i++) - { - GC.Collect(); - } - } + // Collect all the solutions the remote workspace has pinned. + await _remoteWorkspace.AddPinnedSolutionsAsync(pinnedSolutions, cancellationToken).ConfigureAwait(false); - // update gc run time - _lastGCRun = current; - } + // Then add all relevant info from those pinned solutions so the set that we will not let go of. + foreach (var pinnedSolution in pinnedSolutions) + await AddPinnedChecksumsAsync(pinnedSolution).ConfigureAwait(false); + + return; - private async ValueTask CleanAssetsWorkerAsync(CancellationToken cancellationToken) + async ValueTask AddPinnedChecksumsAsync(Solution pinnedSolution) { - if (_assets.IsEmpty) + // Get the checksums for the local solution. Note that this will ensure that all child checksums are + // computed. As such, we can just use TryGetXXX below to get them. + var compilationState = pinnedSolution.CompilationState; + var compilationStateChecksums = await compilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + compilationStateChecksums.AddAllTo(pinnedChecksums); + + var solutionState = compilationState.SolutionState; + Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(out var stateChecksums)); + stateChecksums.AddAllTo(pinnedChecksums); + + foreach (var (_, projectState) in solutionState.ProjectStates) { - // no asset, nothing to do. - return; + Contract.ThrowIfFalse(projectState.TryGetStateChecksums(out var projectStateChecksums)); + projectStateChecksums.AddAllTo(pinnedChecksums); + + AddAll(projectState.DocumentStates); + AddAll(projectState.AdditionalDocumentStates); + AddAll(projectState.AnalyzerConfigDocumentStates); } + } - using (Logger.LogBlock(FunctionId.AssetStorage_CleanAssets, cancellationToken)) + void AddAll(TextDocumentStates states) where TState : TextDocumentState + { + foreach (var (_, documentState) in states.States) { - // Ensure that if our remote workspace has a current solution, that we don't purge any items associated - // with that solution. - PooledHashSet? pinnedChecksums = null; - try - { - foreach (var (checksum, entry) in _assets) - { - // If not enough time has passed, keep in the cache. - if (entry.Stopwatch.Elapsed <= _purgeAfterTimeSpan) - continue; - - // If this is a checksum we want to pin, do not remove it. - if (pinnedChecksums == null) - { - pinnedChecksums = PooledHashSet.GetInstance(); - await AddPinnedChecksumsAsync(pinnedChecksums, cancellationToken).ConfigureAwait(false); - } - - if (pinnedChecksums.Contains(checksum)) - continue; - - _assets.TryRemove(checksum, out _); - } - } - finally - { - pinnedChecksums?.Free(); - } + Contract.ThrowIfFalse(documentState.TryGetStateChecksums(out var documentChecksums)); + documentChecksums.AddAllTo(pinnedChecksums); } } + } - private async ValueTask AddPinnedChecksumsAsync(HashSet pinnedChecksums, CancellationToken cancellationToken) - { - if (_remoteWorkspace is null) - return; + private sealed class Entry + { + public SharedStopwatch Stopwatch = SharedStopwatch.StartNew(); - var checksums = await _remoteWorkspace.CurrentSolution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - checksums.AddAllTo(pinnedChecksums); - } + // This can't change for same checksum + public readonly object Object; - private sealed class Entry + public Entry(object @object) { - public SharedStopwatch Stopwatch = SharedStopwatch.StartNew(); + Object = @object; + } + } - // This can't change for same checksum - public readonly object Object; + public TestAccessor GetTestAccessor() + => new(this); - public Entry(object @object) - { - Object = @object; - } - } + public readonly struct TestAccessor(SolutionAssetCache cache) + { + public void Clear() + => cache._assets.Clear(); } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index 4917033beae78..666b72ae1ee88 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs @@ -3,7 +3,6 @@ // 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.Serialization; @@ -16,22 +15,24 @@ internal sealed class SolutionAssetSource(ServiceBrokerClient client) : IAssetSo { private readonly ServiceBrokerClient _client = client; - public async ValueTask> GetAssetsAsync( + public async ValueTask GetAssetsAsync( Checksum solutionChecksum, - AssetHint assetHint, + AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService serializerService, + Action assetCallback, + TArg arg, CancellationToken cancellationToken) { // Make sure we are on the thread pool to avoid UI thread dependencies if external code uses ConfigureAwait(true) await TaskScheduler.Default; - return await RemoteCallback.InvokeServiceAsync( + await RemoteCallback.InvokeServiceAsync( _client, SolutionAssetProvider.ServiceDescriptor, (callback, cancellationToken) => callback.InvokeAsync( - (proxy, pipeWriter, cancellationToken) => proxy.WriteAssetsAsync(pipeWriter, solutionChecksum, assetHint, checksums, cancellationToken), - (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, solutionChecksum, checksums.Length, serializerService, cancellationToken), + (proxy, pipeWriter, cancellationToken) => proxy.WriteAssetsAsync(pipeWriter, solutionChecksum, assetPath, checksums, cancellationToken), + (pipeReader, cancellationToken) => new RemoteHostAssetReader(pipeReader, solutionChecksum, checksums.Length, serializerService, assetCallback, arg).ReadDataAsync(cancellationToken), cancellationToken), cancellationToken).ConfigureAwait(false); } diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index e942c5e44f5fd..ada4f5f1be1c1 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -40,43 +40,44 @@ internal static async Task AssertChecksumsAsync( AssetProvider assetService, Checksum checksumFromRequest, Solution solutionFromScratch, - Solution incrementalSolutionBuilt) + Solution incrementalSolutionBuilt, + ProjectId? projectConeId) { #if DEBUG var sb = new StringBuilder(); var allChecksumsFromRequest = await GetAllChildrenChecksumsAsync(checksumFromRequest).ConfigureAwait(false); - var assetMapFromNewSolution = await solutionFromScratch.GetAssetMapAsync(CancellationToken.None).ConfigureAwait(false); - var assetMapFromIncrementalSolution = await incrementalSolutionBuilt.GetAssetMapAsync(CancellationToken.None).ConfigureAwait(false); + var assetMapFromNewSolution = await solutionFromScratch.GetAssetMapAsync(projectConeId, CancellationToken.None).ConfigureAwait(false); + var assetMapFromIncrementalSolution = await incrementalSolutionBuilt.GetAssetMapAsync(projectConeId, CancellationToken.None).ConfigureAwait(false); // check 4 things // 1. first see if we create new solution from scratch, it works as expected (indicating a bug in incremental update) var mismatch1 = assetMapFromNewSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList(); - AppendMismatch(mismatch1, "assets only in new solutoin but not in the request", sb); + AppendMismatch(mismatch1, "Assets only in new solution but not in the request", sb); // 2. second check what items is mismatching for incremental solution var mismatch2 = assetMapFromIncrementalSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList(); - AppendMismatch(mismatch2, "assets only in the incremental solution but not in the request", sb); + AppendMismatch(mismatch2, "Assets only in the incremental solution but not in the request", sb); // 3. check whether solution created from scratch and incremental one have any mismatch var mismatch3 = assetMapFromNewSolution.Where(p => !assetMapFromIncrementalSolution.ContainsKey(p.Key)).ToList(); - AppendMismatch(mismatch3, "assets only in new solution but not in incremental solution", sb); + AppendMismatch(mismatch3, "Assets only in new solution but not in incremental solution", sb); var mismatch4 = assetMapFromIncrementalSolution.Where(p => !assetMapFromNewSolution.ContainsKey(p.Key)).ToList(); - AppendMismatch(mismatch4, "assets only in incremental solution but not in new solution", sb); + AppendMismatch(mismatch4, "Assets only in incremental solution but not in new solution", sb); // 4. see what item is missing from request var mismatch5 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromNewSolution.Keys)).ConfigureAwait(false); - AppendMismatch(mismatch5, "assets only in the request but not in new solution", sb); + AppendMismatch(mismatch5, "Assets only in the request but not in new solution", sb); var mismatch6 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromIncrementalSolution.Keys)).ConfigureAwait(false); - AppendMismatch(mismatch6, "assets only in the request but not in incremental solution", sb); + AppendMismatch(mismatch6, "Assets only in the request but not in incremental solution", sb); var result = sb.ToString(); if (result.Length > 0) { Logger.Log(FunctionId.SolutionCreator_AssetDifferences, result); - Debug.Fail("Differences detected in solution checksum: " + result); + Debug.Fail($"Differences detected in solution checksum (ProjectId={projectConeId}):\r\n{result}"); } return; @@ -103,8 +104,8 @@ async Task>> GetAssetFromAssetServiceAsync(I foreach (var checksum in checksums) { - items.Add(new KeyValuePair(checksum, await assetService.GetAssetAsync( - assetHint: AssetHint.None, checksum, CancellationToken.None).ConfigureAwait(false))); + items.Add(KeyValuePairUtil.Create(checksum, await assetService.GetAssetAsync( + AssetPath.FullLookupForTesting, checksum, CancellationToken.None).ConfigureAwait(false))); } return items; @@ -115,9 +116,9 @@ async Task> GetAllChildrenChecksumsAsync(Checksum solutionChec var set = new HashSet(); var solutionCompilationChecksums = await assetService.GetAssetAsync( - assetHint: AssetHint.None, solutionChecksum, CancellationToken.None).ConfigureAwait(false); + AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, CancellationToken.None).ConfigureAwait(false); var solutionChecksums = await assetService.GetAssetAsync( - assetHint: AssetHint.None, solutionCompilationChecksums.SolutionState, CancellationToken.None).ConfigureAwait(false); + AssetPathKind.SolutionStateChecksums, solutionCompilationChecksums.SolutionState, CancellationToken.None).ConfigureAwait(false); solutionCompilationChecksums.AddAllTo(set); solutionChecksums.AddAllTo(set); @@ -125,27 +126,17 @@ async Task> GetAllChildrenChecksumsAsync(Checksum solutionChec foreach (var (projectChecksum, projectId) in solutionChecksums.Projects) { var projectChecksums = await assetService.GetAssetAsync( - assetHint: projectId, projectChecksum, CancellationToken.None).ConfigureAwait(false); + assetPath: projectId, projectChecksum, CancellationToken.None).ConfigureAwait(false); projectChecksums.AddAllTo(set); - await AddDocumentsAsync(projectId, projectChecksums.Documents, set).ConfigureAwait(false); - await AddDocumentsAsync(projectId, projectChecksums.AdditionalDocuments, set).ConfigureAwait(false); - await AddDocumentsAsync(projectId, projectChecksums.AnalyzerConfigDocuments, set).ConfigureAwait(false); + projectChecksums.Documents.AddAllTo(set); + projectChecksums.AdditionalDocuments.AddAllTo(set); + projectChecksums.AnalyzerConfigDocuments.AddAllTo(set); } return set; } - async Task AddDocumentsAsync(ProjectId projectId, ChecksumsAndIds documents, HashSet checksums) - { - foreach (var (documentChecksum, documentId) in documents) - { - var documentChecksums = await assetService.GetAssetAsync( - assetHint: documentId, documentChecksum, CancellationToken.None).ConfigureAwait(false); - AddAllTo(documentChecksums, checksums); - } - } - #else // have this to avoid error on async @@ -161,13 +152,13 @@ private static void AddAllTo(DocumentStateChecksums documentStateChecksums, Hash } /// - /// create checksum to correspoing object map from solution - /// this map should contain every parts of solution that can be used to re-create the solution back + /// create checksum to corresponding object map from solution this map should contain every parts of solution + /// that can be used to re-create the solution back /// - public static async Task> GetAssetMapAsync(this Solution solution, CancellationToken cancellationToken) + public static async Task> GetAssetMapAsync(this Solution solution, ProjectId? projectConeId, CancellationToken cancellationToken) { var map = new Dictionary(); - await solution.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); + await solution.AppendAssetMapAsync(map, projectConeId, cancellationToken).ConfigureAwait(false); return map; } @@ -194,20 +185,22 @@ public static Task AppendAssetMapAsync(this Solution solution, Dictionary map, ProjectId? projectId, CancellationToken cancellationToken) { + var callback = static (Checksum checksum, object asset, Dictionary map) => { map[checksum] = asset; }; + if (projectId == null) { var compilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, assetHint: AssetHint.None, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(compilationChecksums), callback, map, cancellationToken).ConfigureAwait(false); foreach (var frozenSourceGeneratedDocumentState in solution.CompilationState.FrozenSourceGeneratedDocumentStates?.States.Values ?? []) { var documentChecksums = await frozenSourceGeneratedDocumentState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, assetHint: AssetHint.None, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(documentChecksums), callback, map, cancellationToken).ConfigureAwait(false); } var solutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(solutionChecksums.ProjectCone != null); - await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone: null, assetHint: AssetHint.None, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(solutionChecksums), callback, map, cancellationToken).ConfigureAwait(false); foreach (var project in solution.Projects) await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); @@ -215,11 +208,11 @@ public static async Task AppendAssetMapAsync( else { var (compilationChecksums, projectCone) = await solution.CompilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone, assetHint: projectId, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(compilationChecksums), callback, map, cancellationToken).ConfigureAwait(false); var solutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(projectCone.Equals(solutionChecksums.ProjectCone)); - await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone, assetHint: projectId, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(solutionChecksums), callback, map, cancellationToken).ConfigureAwait(false); var project = solution.GetRequiredProject(projectId); await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); @@ -235,13 +228,15 @@ private static async Task AppendAssetMapAsync(this Project project, Dictionary map) => { map[checksum] = asset; }; + var projectChecksums = await project.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await projectChecksums.FindAsync(project.State, hintDocument: null, Flatten(projectChecksums), map, cancellationToken).ConfigureAwait(false); + await projectChecksums.FindAsync(project.State, AssetPath.FullLookupForTesting, Flatten(projectChecksums), callback, map, cancellationToken).ConfigureAwait(false); foreach (var document in project.Documents.Concat(project.AdditionalDocuments).Concat(project.AnalyzerConfigDocuments)) { var documentChecksums = await document.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await documentChecksums.FindAsync(document.State, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); + await documentChecksums.FindAsync(AssetPathKind.Documents, document.State, Flatten(documentChecksums), callback, map, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs index ea25f6948ee99..c86c7482b169d 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs @@ -3,111 +3,113 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger; -namespace Microsoft.CodeAnalysis.Remote +namespace Microsoft.CodeAnalysis.Remote; + +/// +/// This service is used by the SolutionChecksumUpdater to proactively update the solution snapshot in the +/// out-of-process workspace. We do this to limit the amount of time required to synchronize a solution over after +/// an edit once a feature is asking for a snapshot. +/// +internal sealed class RemoteAssetSynchronizationService(in BrokeredServiceBase.ServiceConstructionArguments arguments) + : BrokeredServiceBase(in arguments), IRemoteAssetSynchronizationService { - /// - /// This service is used by the SolutionChecksumUpdater to proactively update the solution snapshot in the - /// out-of-process workspace. We do this to limit the amount of time required to synchronize a solution over after - /// an edit once a feature is asking for a snapshot. - /// - internal sealed class RemoteAssetSynchronizationService : BrokeredServiceBase, IRemoteAssetSynchronizationService + internal sealed class Factory : FactoryBase { - internal sealed class Factory : FactoryBase - { - protected override IRemoteAssetSynchronizationService CreateService(in ServiceConstructionArguments arguments) - => new RemoteAssetSynchronizationService(in arguments); - } + protected override IRemoteAssetSynchronizationService CreateService(in ServiceConstructionArguments arguments) + => new RemoteAssetSynchronizationService(in arguments); + } - public RemoteAssetSynchronizationService(in ServiceConstructionArguments arguments) - : base(in arguments) + public ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken) + { + return RunServiceAsync(async cancellationToken => { - } + using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizePrimaryWorkspaceAsync, Checksum.GetChecksumLogInfo, solutionChecksum, cancellationToken)) + { + var workspace = GetWorkspace(); + var assetProvider = workspace.CreateAssetProvider(solutionChecksum, WorkspaceManager.SolutionAssetCache, SolutionAssetSource); + await workspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, cancellationToken).ConfigureAwait(false); + } + }, cancellationToken); + } - public ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, int workspaceVersion, CancellationToken cancellationToken) + public ValueTask SynchronizeActiveDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken) + { + var documentTrackingService = GetWorkspace().Services.GetRequiredService() as RemoteDocumentTrackingService; + documentTrackingService?.SetActiveDocument(documentId); + return ValueTaskFactory.CompletedTask; + } + + public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, ImmutableArray textChanges, CancellationToken cancellationToken) + { + return RunServiceAsync(async cancellationToken => { - return RunServiceAsync(async cancellationToken => + var workspace = GetWorkspace(); + + using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizeTextAsync, Checksum.GetChecksumLogInfo, baseTextChecksum, cancellationToken)) { - using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizePrimaryWorkspaceAsync, Checksum.GetChecksumLogInfo, solutionChecksum, cancellationToken)) + // Try to get the text associated with baseTextChecksum + var text = await TryGetSourceTextAsync(WorkspaceManager, workspace, documentId, baseTextChecksum, cancellationToken).ConfigureAwait(false); + if (text == null) { - var workspace = GetWorkspace(); - var assetProvider = workspace.CreateAssetProvider(solutionChecksum, WorkspaceManager.SolutionAssetCache, SolutionAssetSource); - await workspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, workspaceVersion, cancellationToken).ConfigureAwait(false); + // it won't bring in base text if it is not there already. + // text needed will be pulled in when there is request + return; } - }, cancellationToken); - } - public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, IEnumerable textChanges, CancellationToken cancellationToken) - { - return RunServiceAsync(async cancellationToken => - { - var workspace = GetWorkspace(); - - using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizeTextAsync, Checksum.GetChecksumLogInfo, baseTextChecksum, cancellationToken)) - { - var serializer = workspace.Services.GetRequiredService(); + // Now attempt to manually apply the edit, producing the new forked text. Store that directly in + // the asset cache so that future calls to retrieve it can do so quickly, without synchronizing over + // the entire document. + var newText = text.WithChanges(textChanges); + var newSerializableText = new SerializableSourceText(newText, newText.GetContentHash()); - // Try to get the text associated with baseTextChecksum - var text = await TryGetSourceTextAsync(WorkspaceManager, workspace, documentId, baseTextChecksum, cancellationToken).ConfigureAwait(false); - if (text == null) - { - // it won't bring in base text if it is not there already. - // text needed will be pulled in when there is request - return; - } + WorkspaceManager.SolutionAssetCache.GetOrAdd(newSerializableText.ContentChecksum, newSerializableText); + } - // Now attempt to manually apply the edit, producing the new forked text. Store that directly in - // the asset cache so that future calls to retrieve it can do so quickly, without synchronizing over - // the entire document. - var newText = new SerializableSourceText(text.WithChanges(textChanges)); - var newChecksum = serializer.CreateChecksum(newText, cancellationToken); + return; - WorkspaceManager.SolutionAssetCache.GetOrAdd(newChecksum, newText); + async static Task TryGetSourceTextAsync( + RemoteWorkspaceManager workspaceManager, + Workspace workspace, + DocumentId documentId, + Checksum baseTextChecksum, + CancellationToken cancellationToken) + { + // check the cheap and fast one first. + // see if the cache has the source text + if (workspaceManager.SolutionAssetCache.TryGetAsset(baseTextChecksum, out var serializableSourceText)) + { + return await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); } - return; - - async static Task TryGetSourceTextAsync( - RemoteWorkspaceManager workspaceManager, - Workspace workspace, - DocumentId documentId, - Checksum baseTextChecksum, - CancellationToken cancellationToken) + // do slower one + // check whether existing solution has it + var document = workspace.CurrentSolution.GetDocument(documentId); + if (document == null) { - // check the cheap and fast one first. - // see if the cache has the source text - if (workspaceManager.SolutionAssetCache.TryGetAsset(baseTextChecksum, out var serializableSourceText)) - { - return await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); - } - - // do slower one - // check whether existing solution has it - var document = workspace.CurrentSolution.GetDocument(documentId); - if (document == null) - { - return null; - } - - // check checksum whether it is there. - // since we lazily synchronize whole solution (SynchronizePrimaryWorkspaceAsync) when things are idle, - // soon or later this will get hit even if text changes got out of sync due to issues in VS side - // such as file is first opened and there is no SourceText in memory yet. - if (!document.State.TryGetStateChecksums(out var state) || - !state.Text.Equals(baseTextChecksum)) - { - return null; - } + return null; + } - return await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + // check checksum whether it is there. + // since we lazily synchronize whole solution (SynchronizePrimaryWorkspaceAsync) when things are idle, + // soon or later this will get hit even if text changes got out of sync due to issues in VS side + // such as file is first opened and there is no SourceText in memory yet. + if (!document.State.TryGetStateChecksums(out var state) || + !state.Text.Equals(baseTextChecksum)) + { + return null; } - }, cancellationToken); - } + + return await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + } + }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs b/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs index 0b331f550e052..1b405b7c66a44 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs @@ -41,7 +41,7 @@ public ValueTask IsCompletedAsync(ImmutableArray featureNames, Can var exportProvider = workspace.Services.SolutionServices.ExportProvider; var listenerProvider = exportProvider.GetExports().Single().Value; - return new ValueTask(!listenerProvider.HasPendingWaiter(featureNames.ToArray())); + return new ValueTask(!listenerProvider.HasPendingWaiter([.. featureNames])); }, cancellationToken); } @@ -53,7 +53,7 @@ public ValueTask ExpeditedWaitAsync(ImmutableArray featureNames, Cancell var exportProvider = workspace.Services.SolutionServices.ExportProvider; var listenerProvider = exportProvider.GetExports().Single().Value; - await listenerProvider.WaitAllAsync(workspace, featureNames.ToArray()).ConfigureAwait(false); + await listenerProvider.WaitAllAsync(workspace, [.. featureNames]).ConfigureAwait(false); }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs index cef12b7393db0..d73d1bc31c709 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs @@ -398,7 +398,7 @@ private async Task AnalyzeAsync( ImmutableDictionary builderMap, BidirectionalMap analyzerToIdMap) { - using var _ = ArrayBuilder<(string analyzerId, SerializableDiagnosticMap diagnosticMap)>.GetInstance(out var diagnostics); + var diagnostics = new FixedSizeArrayBuilder<(string analyzerId, SerializableDiagnosticMap diagnosticMap)>(builderMap.Count); foreach (var (analyzer, analyzerResults) in builderMap) { @@ -412,7 +412,7 @@ private async Task AnalyzeAsync( analyzerResults.Others))); } - return diagnostics.ToImmutable(); + return diagnostics.MoveToImmutable(); } private static ImmutableArray<(string analyzerId, AnalyzerTelemetryInfo)> GetTelemetryInfo( @@ -444,7 +444,7 @@ private async Task AnalyzeAsync( } } - return telemetryBuilder.ToImmutable(); + return telemetryBuilder.ToImmutableAndClear(); } private static string GetAnalyzerId(BidirectionalMap analyzerMap, DiagnosticAnalyzer analyzer) @@ -468,7 +468,7 @@ private static ImmutableArray GetAnalyzers(BidirectionalMap< } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private async Task<(CompilationWithAnalyzers? compilationWithAnalyzers, BidirectionalMap analyzerToIdMap)> GetOrCreateCompilationWithAnalyzersAsync(CancellationToken cancellationToken) diff --git a/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs b/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs index 9a630c45e737d..e75bccf52ca21 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.DocumentHighlighting; @@ -36,6 +34,9 @@ public ValueTask> GetDocumentHigh return RunServiceAsync(solutionChecksum, async solution => { var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + document = options.FrozenPartialSemantics ? document.WithFrozenPartialSemantics(cancellationToken) : document; + solution = document.Project.Solution; + var documentsToSearch = await documentIdsToSearch.SelectAsArrayAsync(id => solution.GetDocumentAsync(id, includeSourceGenerated: true, cancellationToken)).ConfigureAwait(false); var documentsToSearchSet = ImmutableHashSet.CreateRange(documentsToSearch.WhereNotNull()); diff --git a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs index 4cf1d705c6ab4..89b49c0beb9c3 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs @@ -95,24 +95,24 @@ public ValueTask StartDebuggingSessionAsync(Checksum solutio /// /// Remote API. /// - public ValueTask> BreakStateOrCapabilitiesChangedAsync(DebuggingSessionId sessionId, bool? inBreakState, CancellationToken cancellationToken) + public ValueTask BreakStateOrCapabilitiesChangedAsync(DebuggingSessionId sessionId, bool? inBreakState, CancellationToken cancellationToken) { return RunServiceAsync(cancellationToken => { - GetService().BreakStateOrCapabilitiesChanged(sessionId, inBreakState, out var documentsToReanalyze); - return new ValueTask>(documentsToReanalyze); + GetService().BreakStateOrCapabilitiesChanged(sessionId, inBreakState); + return ValueTaskFactory.CompletedTask; }, cancellationToken); } /// /// Remote API. /// - public ValueTask> EndDebuggingSessionAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken) + public ValueTask EndDebuggingSessionAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken) { return RunServiceAsync(cancellationToken => { - GetService().EndDebuggingSession(sessionId, out var documentsToReanalyze); - return new ValueTask>(documentsToReanalyze); + GetService().EndDebuggingSession(sessionId); + return ValueTaskFactory.CompletedTask; }, cancellationToken); } @@ -175,12 +175,12 @@ private static ImmutableArray GetUnexpectedUpdateError(Solution /// /// Remote API. /// - public ValueTask> CommitSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken) + public ValueTask CommitSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken) { return RunServiceAsync(cancellationToken => { - GetService().CommitSolutionUpdate(sessionId, out var documentsToReanalyze); - return new ValueTask>(documentsToReanalyze); + GetService().CommitSolutionUpdate(sessionId); + return ValueTaskFactory.CompletedTask; }, cancellationToken); } diff --git a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs index e267243f8c32d..22b79b01d17a3 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs @@ -30,18 +30,18 @@ public RemoteNavigateToSearchService(in ServiceConstructionArguments arguments, _callback = callback; } - private (Func onItemFound, Func onProjectCompleted) GetCallbacks( + private (Func, Task> onItemsFound, Func onProjectCompleted) GetCallbacks( RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) { - Func onItemFound = async i => await _callback.InvokeAsync((callback, _) => - callback.OnResultFoundAsync(callbackId, i), + Func, Task> onItemsFound = async i => await _callback.InvokeAsync((callback, cancellationToken) => + callback.OnItemsFoundAsync(callbackId, i), cancellationToken).ConfigureAwait(false); - Func onProjectCompleted = async () => await _callback.InvokeAsync((callback, _) => + Func onProjectCompleted = async () => await _callback.InvokeAsync((callback, cancellationToken) => callback.OnProjectCompletedAsync(callbackId), cancellationToken).ConfigureAwait(false); - return (onItemFound, onProjectCompleted); + return (onItemsFound, onProjectCompleted); } public ValueTask HydrateAsync(Checksum solutionChecksum, CancellationToken cancellationToken) @@ -64,10 +64,10 @@ public ValueTask SearchDocumentAsync( return RunServiceAsync(solutionChecksum, async solution => { var document = solution.GetRequiredDocument(documentId); - var (onItemFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); + var (onItemsFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); await AbstractNavigateToSearchService.SearchDocumentInCurrentProcessAsync( - document, searchPattern, kinds.ToImmutableHashSet(), onItemFound, cancellationToken).ConfigureAwait(false); + document, searchPattern, kinds.ToImmutableHashSet(), onItemsFound, cancellationToken).ConfigureAwait(false); }, cancellationToken); } @@ -83,12 +83,12 @@ public ValueTask SearchProjectsAsync( return RunServiceAsync(solutionChecksum, async solution => { var projects = projectIds.SelectAsArray(solution.GetRequiredProject); - var (onItemFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); + var (onItemsFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); var priorityDocuments = priorityDocumentIds.SelectAsArray(d => solution.GetRequiredDocument(d)); await AbstractNavigateToSearchService.SearchProjectsInCurrentProcessAsync( - projects, priorityDocuments, searchPattern, kinds.ToImmutableHashSet(), onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + projects, priorityDocuments, searchPattern, kinds.ToImmutableHashSet(), onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); }, cancellationToken); } @@ -103,10 +103,10 @@ public ValueTask SearchGeneratedDocumentsAsync( return RunServiceAsync(solutionChecksum, async solution => { var projects = projectIds.SelectAsArray(solution.GetRequiredProject); - var (onItemFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); + var (onItemsFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); await AbstractNavigateToSearchService.SearchGeneratedDocumentsInCurrentProcessAsync( - projects, searchPattern, kinds.ToImmutableHashSet(), onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + projects, searchPattern, kinds.ToImmutableHashSet(), onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); }, cancellationToken); } @@ -123,10 +123,10 @@ public ValueTask SearchCachedDocumentsAsync( // Intentionally do not call GetSolutionAsync here. We do not want the cost of // synchronizing the solution over to the remote side. Instead, we just directly // check whatever cached data we have from the previous vs session. - var (onItemFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); + var (onItemsFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); var storageService = GetWorkspaceServices().GetPersistentStorageService(); await AbstractNavigateToSearchService.SearchCachedDocumentsInCurrentProcessAsync( - storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds.ToImmutableHashSet(), onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds.ToImmutableHashSet(), onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/RemoteDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/RemoteDocumentTrackingService.cs new file mode 100644 index 0000000000000..4a83c398ac9b6 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/RemoteDocumentTrackingService.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Remote; + +[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Host), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class RemoteDocumentTrackingService() : IDocumentTrackingService +{ + private DocumentId? _activeDocument; + + public event EventHandler? ActiveDocumentChanged; + + public ImmutableArray GetVisibleDocuments() + => []; + + public DocumentId? TryGetActiveDocument() + => _activeDocument; + + internal void SetActiveDocument(DocumentId? documentId) + { + _activeDocument = documentId; + ActiveDocumentChanged?.Invoke(this, documentId); + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs index 1abec95dd1e99..6bb8113b40e18 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs @@ -117,7 +117,6 @@ private static async Task CacheClassificationsAsync( var persistenceService = solution.Services.GetPersistentStorageService(); var storage = await persistenceService.GetStorageAsync(SolutionKey.ToSolutionKey(solution), cancellationToken).ConfigureAwait(false); - await using var _1 = storage.ConfigureAwait(false); if (storage == null) return; @@ -156,7 +155,7 @@ private static async Task CacheClassificationsAsync( } using var stream = SerializableBytes.CreateWritableStream(); - using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) + using (var writer = new ObjectWriter(stream, leaveOpen: true)) { WriteTo(classifiedSpans, writer); } @@ -280,13 +279,12 @@ private async Task> TryReadCachedSemanticClassifi { var persistenceService = GetWorkspaceServices().GetPersistentStorageService(); var storage = await persistenceService.GetStorageAsync(documentKey.Project.Solution, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); if (storage == null) return default; var persistenceName = GetPersistenceName(type); using var stream = await storage.ReadStreamAsync(documentKey, persistenceName, checksum, cancellationToken).ConfigureAwait(false); - using var reader = ObjectReader.TryGetReader(stream, cancellationToken: cancellationToken); + using var reader = ObjectReader.TryGetReader(stream); if (reader == null) return default; @@ -310,7 +308,7 @@ private static ImmutableArray Read(ObjectReader reader) classificationTypes.Add(reader.ReadRequiredString()); var classifiedSpanCount = reader.ReadInt32(); - using var _2 = ArrayBuilder.GetInstance(classifiedSpanCount, out var classifiedSpans); + var classifiedSpans = new FixedSizeArrayBuilder(classifiedSpanCount); for (var i = 0; i < classifiedSpanCount; i++) { @@ -324,7 +322,7 @@ private static ImmutableArray Read(ObjectReader reader) } } - return classifiedSpans.ToImmutableAndClear(); + return classifiedSpans.MoveToImmutable(); } catch { diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs index e4174e8f2d1cf..e593b1d8928de 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs @@ -33,11 +33,9 @@ public ValueTask GetClassificationsAsync( var document = solution.GetDocument(documentId) ?? await solution.GetSourceGeneratedDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(document); - if (options.ForceFrozenPartialSemanticsForCrossProcessOperations) - { - // Frozen partial semantics is not automatically passed to OOP, so enable it explicitly when desired - document = document.WithFrozenPartialSemantics(cancellationToken); - } + // Frozen partial semantics is not automatically passed to OOP, so enable it explicitly when desired + document = options.FrozenPartialSemantics ? document.WithFrozenPartialSemantics(cancellationToken) : document; + solution = document.Project.Solution; using var _ = Classifier.GetPooledList(out var temp); await AbstractClassificationService.AddClassificationsInCurrentProcessAsync( @@ -54,7 +52,7 @@ await AbstractClassificationService.AddClassificationsInCurrentProcessAsync( _workQueue.AddWork((document, type, options)); } - return SerializableClassifiedSpans.Dehydrate(temp.ToImmutableArray()); + return SerializableClassifiedSpans.Dehydrate([.. temp]); }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticSearch/RemoteSemanticSearchService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticSearch/RemoteSemanticSearchService.cs new file mode 100644 index 0000000000000..8446af1308920 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticSearch/RemoteSemanticSearchService.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; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.SemanticSearch; + +namespace Microsoft.CodeAnalysis.Remote; + +internal sealed class RemoteSemanticSearchService( + in BrokeredServiceBase.ServiceConstructionArguments arguments, + RemoteCallback callback) + : BrokeredServiceBase(arguments), IRemoteSemanticSearchService +{ + internal sealed class Factory : FactoryBase + { + protected override IRemoteSemanticSearchService CreateService(in ServiceConstructionArguments arguments, RemoteCallback callback) + => new RemoteSemanticSearchService(arguments, callback); + } + + private sealed class ClientCallbacks( + RemoteCallback callback, + RemoteServiceCallbackId callbackId) : ISemanticSearchResultsObserver, OptionsProvider + { + public ValueTask GetOptionsAsync(CodeAnalysis.Host.LanguageServices languageServices, CancellationToken cancellationToken) + => callback.InvokeAsync((callback, cancellationToken) => callback.GetClassificationOptionsAsync(callbackId, languageServices.Language, cancellationToken), cancellationToken); + + public ValueTask AddItemsAsync(int itemCount, CancellationToken cancellationToken) + => callback.InvokeAsync((callback, cancellationToken) => callback.AddItemsAsync(callbackId, itemCount, cancellationToken), cancellationToken); + + public ValueTask ItemsCompletedAsync(int itemCount, CancellationToken cancellationToken) + => callback.InvokeAsync((callback, cancellationToken) => callback.ItemsCompletedAsync(callbackId, itemCount, cancellationToken), cancellationToken); + + public ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) + { + var dehydratedDefinition = SerializableDefinitionItem.Dehydrate(id: 0, definition); + return callback.InvokeAsync((callback, cancellationToken) => callback.OnDefinitionFoundAsync(callbackId, dehydratedDefinition, cancellationToken), cancellationToken); + } + + public ValueTask OnUserCodeExceptionAsync(UserCodeExceptionInfo exception, CancellationToken cancellationToken) + => callback.InvokeAsync((callback, cancellationToken) => callback.OnUserCodeExceptionAsync(callbackId, exception, cancellationToken), cancellationToken); + + public ValueTask OnCompilationFailureAsync(ImmutableArray errors, CancellationToken cancellationToken) + => callback.InvokeAsync((callback, cancellationToken) => callback.OnCompilationFailureAsync(callbackId, errors, cancellationToken), cancellationToken); + } + + /// + /// Remote API. + /// + public ValueTask ExecuteQueryAsync( + Checksum solutionChecksum, + RemoteServiceCallbackId callbackId, + string language, + string query, + string referenceAssembliesDir, + CancellationToken cancellationToken) + { + return RunServiceAsync(solutionChecksum, async solution => + { + var service = solution.Services.GetLanguageServices(language).GetService(); + if (service == null) + { + return new ExecuteQueryResult(FeaturesResources.Semantic_search_only_supported_on_net_core); + } + + var clientCallbacks = new ClientCallbacks(callback, callbackId); + + return await service.ExecuteQueryAsync(solution, query, referenceAssembliesDir, clientCallbacks, clientCallbacks, TraceLogger, cancellationToken).ConfigureAwait(false); + }, cancellationToken); + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs deleted file mode 100644 index d3cbbf091565b..0000000000000 --- a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Diagnostics; -using 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 []; - } - - public DocumentId? TryGetActiveDocument() - { - Fail("Code should not be attempting to obtain active document from a stateless remote invocation."); - return null; - } - - private static void Fail(string message) - { - // assert in debug builds to hopefully catch problems in CI - Debug.Fail(message); - - // record NFW to see who violates contract. - FatalError.ReportAndCatch(new InvalidOperationException(message)); - } - } -} diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 05b74c0a06f1d..fa6ca45e97d70 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -2,9 +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.Generic; using System.Collections.Immutable; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SourceGeneration; @@ -12,6 +18,9 @@ namespace Microsoft.CodeAnalysis.Remote; +// Can use AnalyzerReference as a key here as we will will always get back the same instance back for the same checksum. +using AnalyzerReferenceMap = ConditionalWeakTable>; + internal sealed partial class RemoteSourceGenerationService(in BrokeredServiceBase.ServiceConstructionArguments arguments) : BrokeredServiceBase(arguments), IRemoteSourceGenerationService { @@ -21,7 +30,7 @@ protected override IRemoteSourceGenerationService CreateService(in ServiceConstr => new RemoteSourceGenerationService(arguments); } - public ValueTask> GetSourceGenerationInfoAsync( + public ValueTask> GetSourceGenerationInfoAsync( Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken) { return RunServiceAsync(solutionChecksum, async solution => @@ -29,15 +38,14 @@ protected override IRemoteSourceGenerationService CreateService(in ServiceConstr var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity)>.GetInstance(documentStates.Ids.Count, out var result); - + var result = new FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>(documentStates.States.Count); foreach (var (id, state) in documentStates.States) { Contract.ThrowIfFalse(id.IsSourceGenerated); - result.Add((state.Identity, state.GetContentIdentity())); + result.Add((state.Identity, state.GetContentIdentity(), state.GenerationDateTime)); } - return result.ToImmutableAndClear(); + return result.MoveToImmutable(); }, cancellationToken); } @@ -49,8 +57,7 @@ public ValueTask> GetContentsAsync( var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(documentIds.Length, out var result); - + var result = new FixedSizeArrayBuilder(documentIds.Length); foreach (var id in documentIds) { Contract.ThrowIfFalse(id.IsSourceGenerated); @@ -59,7 +66,69 @@ public ValueTask> GetContentsAsync( result.Add(text.ToString()); } - return result.ToImmutableAndClear(); + return result.MoveToImmutable(); }, cancellationToken); } + + private static readonly Dictionary s_languageToAnalyzerReferenceMap = new() + { + { LanguageNames.CSharp, (new(), static analyzerReference => HasSourceGenerators(analyzerReference, LanguageNames.CSharp)) }, + { LanguageNames.VisualBasic, (new(), static analyzerReference => HasSourceGenerators(analyzerReference, LanguageNames.VisualBasic)) }, + }; + + private static StrongBox HasSourceGenerators( + AnalyzerReference analyzerReference, string language) + { + var generators = analyzerReference.GetGenerators(language); + return new(generators.Any()); + } + + public async ValueTask HasGeneratorsAsync( + Checksum solutionChecksum, + ProjectId projectId, + ImmutableArray analyzerReferenceChecksums, + string language, + CancellationToken cancellationToken) + { + if (analyzerReferenceChecksums.Length == 0) + return false; + + // Do not use RunServiceAsync here. We don't want to actually synchronize a solution instance on this remote + // side to service this request. Specifically, solution syncing is expensive, and will pull over a lot of data + // that we don't need (like document contents). All we need to do is synchronize over the analyzer-references + // (which are actually quite small as they are represented as file-paths), and then answer the question based on + // them directly. We can then cache that result for future requests. + var workspace = GetWorkspace(); + var assetProvider = workspace.CreateAssetProvider(solutionChecksum, WorkspaceManager.SolutionAssetCache, SolutionAssetSource); + + using var _1 = PooledHashSet.GetInstance(out var checksums); + checksums.AddRange(analyzerReferenceChecksums); + + // Fetch the analyzer references specified by the host. Note: this will only serialize this information over + // the first time needed. After that, it will be cached in the WorkspaceManager.SolutionAssetCache on the remote + // side, so it will be a no-op to fetch them in the future. + // + // From this point on, the host won't call into us for the same project-state (as it caches the data itself). If + // the project state changes, it will just call into us with the checksums for its analyzer references. As + // those will almost always be the same, we'll just fetch the precomputed values on our end, return them, and + // the host will cache it. We'll only actually fetch something new and compute something new when an actual new + // analyzer reference is added. + using var _2 = ArrayBuilder.GetInstance(checksums.Count, out var analyzerReferences); + await assetProvider.GetAssetHelper().GetAssetsAsync( + projectId, + checksums, + static (_, analyzerReference, analyzerReferences) => analyzerReferences.Add(analyzerReference), + analyzerReferences, + cancellationToken).ConfigureAwait(false); + + var (analyzerReferenceMap, callback) = s_languageToAnalyzerReferenceMap[language]; + foreach (var analyzerReference in analyzerReferences) + { + var hasGenerators = analyzerReferenceMap.GetValue(analyzerReference, callback); + if (hasGenerators.Value) + return true; + } + + return false; + } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index 5d38cc493c2ab..83aef0424bc2b 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -82,12 +82,12 @@ await SymbolFinder.FindLiteralReferencesInCurrentProcessAsync( private static ImmutableArray Convert(ImmutableArray items, Solution solution, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var result); + var result = new FixedSizeArrayBuilder(items.Length); foreach (var item in items) result.Add(SerializableSymbolAndProjectId.Dehydrate(solution, item, cancellationToken)); - return result.ToImmutable(); + return result.MoveToImmutable(); } public ValueTask> FindAllDeclarationsWithNormalQueryAsync( @@ -218,12 +218,6 @@ public ValueTask OnStartedAsync(CancellationToken cancellationToken) public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => _callback.InvokeAsync((callback, cancellationToken) => callback.OnCompletedAsync(_callbackId, cancellationToken), cancellationToken); - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentStartedAsync(_callbackId, document.Id, cancellationToken), cancellationToken); - - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentCompletedAsync(_callbackId, document.Id, cancellationToken), cancellationToken); - public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, cancellationToken); @@ -231,15 +225,18 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can (callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedGroup, cancellationToken), cancellationToken); } - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation reference, CancellationToken cancellationToken) + public ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, + CancellationToken cancellationToken) { - var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, cancellationToken); - var dehydratedDefinition = SerializableSymbolAndProjectId.Dehydrate(_solution, definition, cancellationToken); - var dehydratedReference = SerializableReferenceLocation.Dehydrate(reference, cancellationToken); + var dehydrated = references.SelectAsArray(t => + (SerializableSymbolGroup.Dehydrate(_solution, t.group, cancellationToken), + SerializableSymbolAndProjectId.Dehydrate(_solution, t.symbol, cancellationToken), + SerializableReferenceLocation.Dehydrate(t.location, cancellationToken))); return _callback.InvokeAsync( - (callback, cancellationToken) => callback.OnReferenceFoundAsync( - _callbackId, dehydratedGroup, dehydratedDefinition, dehydratedReference, cancellationToken), cancellationToken); + (callback, cancellationToken) => callback.OnReferencesFoundAsync( + _callbackId, dehydrated, cancellationToken), cancellationToken); } public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CSharpCompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CSharpCompilerExtensions.projitems index 6dd5d1089835f..fa013e05a24b3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CSharpCompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CSharpCompilerExtensions.projitems @@ -10,6 +10,7 @@ + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeGeneration/CSharpSyntaxTokens.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeGeneration/CSharpSyntaxTokens.cs new file mode 100644 index 0000000000000..9b920f587a42d --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeGeneration/CSharpSyntaxTokens.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more 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; + +using static SyntaxFactory; + +internal static class CSharpSyntaxTokens +{ + public static readonly SyntaxToken AbstractKeyword = Token(SyntaxKind.AbstractKeyword); + public static readonly SyntaxToken AssemblyKeyword = Token(SyntaxKind.AssemblyKeyword); + public static readonly SyntaxToken AsyncKeyword = Token(SyntaxKind.AsyncKeyword); + public static readonly SyntaxToken AwaitKeyword = Token(SyntaxKind.AwaitKeyword); + public static readonly SyntaxToken BoolKeyword = Token(SyntaxKind.BoolKeyword); + public static readonly SyntaxToken ByteKeyword = Token(SyntaxKind.ByteKeyword); + public static readonly SyntaxToken CharKeyword = Token(SyntaxKind.CharKeyword); + public static readonly SyntaxToken CheckedKeyword = Token(SyntaxKind.CheckedKeyword); + public static readonly SyntaxToken CloseBraceToken = Token(SyntaxKind.CloseBraceToken); + public static readonly SyntaxToken CloseBracketToken = Token(SyntaxKind.CloseBracketToken); + public static readonly SyntaxToken CloseParenToken = Token(SyntaxKind.CloseParenToken); + public static readonly SyntaxToken ColonToken = Token(SyntaxKind.ColonToken); + public static readonly SyntaxToken CommaToken = Token(SyntaxKind.CommaToken); + public static readonly SyntaxToken ConstKeyword = Token(SyntaxKind.ConstKeyword); + public static readonly SyntaxToken DecimalKeyword = Token(SyntaxKind.DecimalKeyword); + public static readonly SyntaxToken DisableKeyword = Token(SyntaxKind.DisableKeyword); + public static readonly SyntaxToken DotDotToken = Token(SyntaxKind.DotDotToken); + public static readonly SyntaxToken DoubleKeyword = Token(SyntaxKind.DoubleKeyword); + public static readonly SyntaxToken EndOfDocumentationCommentToken = Token(SyntaxKind.EndOfDocumentationCommentToken); + public static readonly SyntaxToken ExplicitKeyword = Token(SyntaxKind.ExplicitKeyword); + public static readonly SyntaxToken ExternKeyword = Token(SyntaxKind.ExternKeyword); + public static readonly SyntaxToken FileKeyword = Token(SyntaxKind.FileKeyword); + public static readonly SyntaxToken FloatKeyword = Token(SyntaxKind.FloatKeyword); + public static readonly SyntaxToken ForEachKeyword = Token(SyntaxKind.ForEachKeyword); + public static readonly SyntaxToken FromKeyword = Token(SyntaxKind.FromKeyword); + public static readonly SyntaxToken GlobalKeyword = Token(SyntaxKind.GlobalKeyword); + public static readonly SyntaxToken GreaterThanEqualsToken = Token(SyntaxKind.GreaterThanEqualsToken); + public static readonly SyntaxToken GreaterThanToken = Token(SyntaxKind.GreaterThanToken); + public static readonly SyntaxToken IfKeyword = Token(SyntaxKind.IfKeyword); + public static readonly SyntaxToken ImplicitKeyword = Token(SyntaxKind.ImplicitKeyword); + public static readonly SyntaxToken InKeyword = Token(SyntaxKind.InKeyword); + public static readonly SyntaxToken InterfaceKeyword = Token(SyntaxKind.InterfaceKeyword); + public static readonly SyntaxToken InternalKeyword = Token(SyntaxKind.InternalKeyword); + public static readonly SyntaxToken InterpolatedStringEndToken = Token(SyntaxKind.InterpolatedStringEndToken); + public static readonly SyntaxToken InterpolatedStringStartToken = Token(SyntaxKind.InterpolatedStringStartToken); + public static readonly SyntaxToken IntKeyword = Token(SyntaxKind.IntKeyword); + public static readonly SyntaxToken IsKeyword = Token(SyntaxKind.IsKeyword); + public static readonly SyntaxToken LessThanEqualsToken = Token(SyntaxKind.LessThanEqualsToken); + public static readonly SyntaxToken LessThanToken = Token(SyntaxKind.LessThanToken); + public static readonly SyntaxToken LetKeyword = Token(SyntaxKind.LetKeyword); + public static readonly SyntaxToken LongKeyword = Token(SyntaxKind.LongKeyword); + public static readonly SyntaxToken MethodKeyword = Token(SyntaxKind.MethodKeyword); + public static readonly SyntaxToken NewKeyword = Token(SyntaxKind.NewKeyword); + public static readonly SyntaxToken NotKeyword = Token(SyntaxKind.NotKeyword); + public static readonly SyntaxToken NullKeyword = Token(SyntaxKind.NullKeyword); + public static readonly SyntaxToken ObjectKeyword = Token(SyntaxKind.ObjectKeyword); + public static readonly SyntaxToken OpenBraceToken = Token(SyntaxKind.OpenBraceToken); + public static readonly SyntaxToken OpenBracketToken = Token(SyntaxKind.OpenBracketToken); + public static readonly SyntaxToken OpenParenToken = Token(SyntaxKind.OpenParenToken); + public static readonly SyntaxToken OperatorKeyword = Token(SyntaxKind.OperatorKeyword); + public static readonly SyntaxToken OutKeyword = Token(SyntaxKind.OutKeyword); + public static readonly SyntaxToken OverrideKeyword = Token(SyntaxKind.OverrideKeyword); + public static readonly SyntaxToken ParamsKeyword = Token(SyntaxKind.ParamsKeyword); + public static readonly SyntaxToken PartialKeyword = Token(SyntaxKind.PartialKeyword); + public static readonly SyntaxToken PlusToken = Token(SyntaxKind.PlusToken); + public static readonly SyntaxToken PrivateKeyword = Token(SyntaxKind.PrivateKeyword); + public static readonly SyntaxToken PropertyKeyword = Token(SyntaxKind.PropertyKeyword); + public static readonly SyntaxToken ProtectedKeyword = Token(SyntaxKind.ProtectedKeyword); + public static readonly SyntaxToken PublicKeyword = Token(SyntaxKind.PublicKeyword); + public static readonly SyntaxToken QuestionQuestionEqualsToken = Token(SyntaxKind.QuestionQuestionEqualsToken); + public static readonly SyntaxToken QuestionToken = Token(SyntaxKind.QuestionToken); + public static readonly SyntaxToken ReadOnlyKeyword = Token(SyntaxKind.ReadOnlyKeyword); + public static readonly SyntaxToken RecordKeyword = Token(SyntaxKind.RecordKeyword); + public static readonly SyntaxToken RefKeyword = Token(SyntaxKind.RefKeyword); + public static readonly SyntaxToken RequiredKeyword = Token(SyntaxKind.RequiredKeyword); + public static readonly SyntaxToken RestoreKeyword = Token(SyntaxKind.RestoreKeyword); + public static readonly SyntaxToken ReturnKeyword = Token(SyntaxKind.ReturnKeyword); + public static readonly SyntaxToken SByteKeyword = Token(SyntaxKind.SByteKeyword); + public static readonly SyntaxToken ScopedKeyword = Token(SyntaxKind.ScopedKeyword); + public static readonly SyntaxToken SealedKeyword = Token(SyntaxKind.SealedKeyword); + public static readonly SyntaxToken SemicolonToken = Token(SyntaxKind.SemicolonToken); + public static readonly SyntaxToken ShortKeyword = Token(SyntaxKind.ShortKeyword); + public static readonly SyntaxToken SlashGreaterThanToken = Token(SyntaxKind.SlashGreaterThanToken); + public static readonly SyntaxToken StaticKeyword = Token(SyntaxKind.StaticKeyword); + public static readonly SyntaxToken StringKeyword = Token(SyntaxKind.StringKeyword); + public static readonly SyntaxToken StructKeyword = Token(SyntaxKind.StructKeyword); + public static readonly SyntaxToken SwitchKeyword = Token(SyntaxKind.SwitchKeyword); + public static readonly SyntaxToken ThisKeyword = Token(SyntaxKind.ThisKeyword); + public static readonly SyntaxToken TildeToken = Token(SyntaxKind.TildeToken); + public static readonly SyntaxToken UIntKeyword = Token(SyntaxKind.UIntKeyword); + public static readonly SyntaxToken ULongKeyword = Token(SyntaxKind.ULongKeyword); + public static readonly SyntaxToken UnmanagedKeyword = Token(SyntaxKind.UnmanagedKeyword); + public static readonly SyntaxToken UnsafeKeyword = Token(SyntaxKind.UnsafeKeyword); + public static readonly SyntaxToken UShortKeyword = Token(SyntaxKind.UShortKeyword); + public static readonly SyntaxToken UsingKeyword = Token(SyntaxKind.UsingKeyword); + public static readonly SyntaxToken VirtualKeyword = Token(SyntaxKind.VirtualKeyword); + public static readonly SyntaxToken VoidKeyword = Token(SyntaxKind.VoidKeyword); + public static readonly SyntaxToken VolatileKeyword = Token(SyntaxKind.VolatileKeyword); + public static readonly SyntaxToken WhereKeyword = Token(SyntaxKind.WhereKeyword); +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs index 04b4aebcf373b..a5b4438a9d83a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeStyle; @@ -155,6 +154,11 @@ private static Option2> CreatePreferE "csharp_prefer_static_local_function", CSharpIdeCodeStyleOptions.Default.PreferStaticLocalFunction); + public static readonly Option2> PreferStaticAnonymousFunction = CreateOption( + CodeStyleOptionGroups.Modifier, + "csharp_prefer_static_anonymous_function", + CSharpIdeCodeStyleOptions.Default.PreferStaticAnonymousFunction); + public static readonly Option2> PreferReadOnlyStruct = CreateOption( CodeStyleOptionGroups.Modifier, "csharp_style_prefer_readonly_struct", diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpIdeCodeStyleOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpIdeCodeStyleOptions.cs index 7e0b6e0ade8ab..e257332c63b21 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpIdeCodeStyleOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpIdeCodeStyleOptions.cs @@ -6,7 +6,6 @@ using System.Collections.Immutable; using System.Linq; using System.Runtime.Serialization; -using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Options; @@ -80,6 +79,7 @@ internal sealed record class CSharpIdeCodeStyleOptions : IdeCodeStyleOptions, IE [DataMember] public CodeStyleOption2 PreferReadOnlyStruct { get; init; } = CodeStyleOption2.TrueWithSuggestionEnforcement; [DataMember] public CodeStyleOption2 PreferReadOnlyStructMember { get; init; } = CodeStyleOption2.TrueWithSuggestionEnforcement; [DataMember] public CodeStyleOption2 PreferStaticLocalFunction { get; init; } = CodeStyleOption2.TrueWithSuggestionEnforcement; + [DataMember] public CodeStyleOption2 PreferStaticAnonymousFunction { get; init; } = CodeStyleOption2.TrueWithSuggestionEnforcement; [DataMember] public CodeStyleOption2 PreferExpressionBodiedLambdas { get; init; } = s_whenPossibleWithSilentEnforcement; [DataMember] public CodeStyleOption2 PreferPrimaryConstructors { get; init; } = CodeStyleOption2.TrueWithSuggestionEnforcement; @@ -121,6 +121,7 @@ internal CSharpIdeCodeStyleOptions(IOptionsReader options, CSharpIdeCodeStyleOpt PreferReadOnlyStruct = options.GetOption(CSharpCodeStyleOptions.PreferReadOnlyStruct, fallbackOptions.PreferReadOnlyStruct); PreferReadOnlyStructMember = options.GetOption(CSharpCodeStyleOptions.PreferReadOnlyStructMember, fallbackOptions.PreferReadOnlyStructMember); PreferStaticLocalFunction = options.GetOption(CSharpCodeStyleOptions.PreferStaticLocalFunction, fallbackOptions.PreferStaticLocalFunction); + PreferStaticAnonymousFunction = options.GetOption(CSharpCodeStyleOptions.PreferStaticAnonymousFunction, fallbackOptions.PreferStaticAnonymousFunction); PreferPrimaryConstructors = options.GetOption(CSharpCodeStyleOptions.PreferPrimaryConstructors, fallbackOptions.PreferPrimaryConstructors); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs index 9e3cc22d54d4b..af8de6ed49ff5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs @@ -8,11 +8,12 @@ using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Extensions; +using static CSharpSyntaxTokens; + internal static partial class ExpressionSyntaxExtensions { [return: NotNullIfNotNull(nameof(expression))] @@ -871,7 +872,7 @@ public static bool TryConvertToStatement( return false; } - var semicolonToken = semicolonTokenOpt ?? SyntaxFactory.Token(SyntaxKind.SemicolonToken); + var semicolonToken = semicolonTokenOpt ?? SemicolonToken; statement = ConvertToStatement(expression, semicolonToken, createReturnStatementForExpression); return true; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs index 5ea6e1a52a234..539db54381412 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs @@ -330,36 +330,29 @@ private static bool RemovalMayIntroduceInterpolationAmbiguity(ParenthesizedExpre // they include any : or :: tokens. If they do, we can't remove the parentheses because // the parser would assume that the first : would begin the format clause of the interpolation. - var stack = s_nodeStackPool.AllocateAndClear(); - try - { - stack.Push(node.Expression); + using var pooledStack = s_nodeStackPool.GetPooledObject(); + var stack = pooledStack.Object; - while (stack.Count > 0) - { - var expression = stack.Pop(); + stack.Push(node.Expression); - foreach (var nodeOrToken in expression.ChildNodesAndTokens()) + while (stack.TryPop(out var expression)) + { + foreach (var nodeOrToken in expression.ChildNodesAndTokens()) + { + // Note: There's no need drill into other parenthesized expressions, since any colons in them would be unambiguous. + if (nodeOrToken.IsNode && !nodeOrToken.IsKind(SyntaxKind.ParenthesizedExpression)) { - // Note: There's no need drill into other parenthesized expressions, since any colons in them would be unambiguous. - if (nodeOrToken.IsNode && !nodeOrToken.IsKind(SyntaxKind.ParenthesizedExpression)) - { - stack.Push(nodeOrToken.AsNode()!); - } - else if (nodeOrToken.IsToken) + stack.Push(nodeOrToken.AsNode()!); + } + else if (nodeOrToken.IsToken) + { + if (nodeOrToken.Kind() is SyntaxKind.ColonToken or SyntaxKind.ColonColonToken) { - if (nodeOrToken.Kind() is SyntaxKind.ColonToken or SyntaxKind.ColonColonToken) - { - return true; - } + return true; } } } } - finally - { - s_nodeStackPool.ClearAndFree(stack); - } return false; } @@ -739,16 +732,16 @@ public static OperatorPrecedence GetOperatorPrecedence(this PatternSyntax patter { switch (pattern) { - case ConstantPatternSyntax _: - case DiscardPatternSyntax _: - case DeclarationPatternSyntax _: - case RecursivePatternSyntax _: - case TypePatternSyntax _: - case VarPatternSyntax _: + case ConstantPatternSyntax: + case DiscardPatternSyntax: + case DeclarationPatternSyntax: + case RecursivePatternSyntax: + case TypePatternSyntax: + case VarPatternSyntax: return OperatorPrecedence.Primary; - case UnaryPatternSyntax _: - case RelationalPatternSyntax _: + case UnaryPatternSyntax: + case RelationalPatternSyntax: return OperatorPrecedence.Unary; case BinaryPatternSyntax binaryPattern: diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs index 3802c800a2d9e..43c0af76af764 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs @@ -33,7 +33,7 @@ public static IEnumerable LookupTypeRegardlessOfArity( } } - return SpecializedCollections.EmptyEnumerable(); + return []; } public static ImmutableArray LookupName( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs index 64ab289e859a3..59c702c08cac1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs @@ -764,7 +764,7 @@ public static IEnumerable GetMembers(this SyntaxNode? n BaseNamespaceDeclarationSyntax @namespace => @namespace.Members, TypeDeclarationSyntax type => type.Members, EnumDeclarationSyntax @enum => @enum.Members, - _ => SpecializedCollections.EmptyEnumerable(), + _ => [], }; public static bool IsInExpressionTree( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs index b44a8e52b3fa2..2e538e86dd901 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs @@ -58,8 +58,7 @@ public IList FormatRange( // Exception 2: Similar behavior for do-while if (common.ContainsDiagnostics && !CloseBraceOfTryOrDoBlock(endToken)) { - smartTokenformattingRules = ImmutableArray.Empty.Add( - new NoLineChangeFormattingRule()).AddRange(_formattingRules); + smartTokenformattingRules = [new NoLineChangeFormattingRule(), .. _formattingRules]; } var formatter = CSharpSyntaxFormatting.Instance; @@ -84,7 +83,7 @@ public IList FormatToken(SyntaxToken token, CancellationToken cancel if (previousToken.Kind() == SyntaxKind.None) { // no previous token. nothing to format - return SpecializedCollections.EmptyList(); + return []; } // This is a heuristic to prevent brace completion from breaking user expectation/muscle memory in common scenarios (see Devdiv:823958). diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index fb05d8fb32a8b..8b2c346421d82 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -259,8 +259,7 @@ public IEnumerable GetDeclaredSymbols( v => semanticModel.GetRequiredDeclaredSymbol(v, cancellationToken)); default: - return SpecializedCollections.SingletonEnumerable( - semanticModel.GetRequiredDeclaredSymbol(memberDeclaration, cancellationToken)); + return [semanticModel.GetRequiredDeclaredSymbol(memberDeclaration, cancellationToken)]; } } @@ -395,7 +394,7 @@ public ImmutableArray GetLocalFunctionSymbols(Compilation compila } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } public bool IsInExpressionTree(SemanticModel semanticModel, SyntaxNode node, [NotNullWhen(true)] INamedTypeSymbol? expressionType, CancellationToken cancellationToken) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index b03846c9f11a5..19bba9c32a156 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -794,9 +794,8 @@ public string GetDisplayName(SyntaxNode? node, DisplayNameOptions options, strin } } - while (!names.IsEmpty()) + while (names.TryPop(out var name)) { - var name = names.Pop(); if (name != null) { builder.Append(name); @@ -1071,9 +1070,7 @@ private static TextSpan GetBlockBodySpan(BlockSyntax body) public IEnumerable GetConstructors(SyntaxNode? root, CancellationToken cancellationToken) { if (root is not CompilationUnitSyntax compilationUnit) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var constructors = new List(); AppendConstructors(compilationUnit.Members, constructors, cancellationToken); @@ -1681,14 +1678,23 @@ public void GetPartsOfNamedMemberInitializer(SyntaxNode node, out SyntaxNode ide expression = assignment.Right; } - public void GetPartsOfObjectCreationExpression(SyntaxNode node, out SyntaxNode type, out SyntaxNode? argumentList, out SyntaxNode? initializer) + public void GetPartsOfObjectCreationExpression(SyntaxNode node, out SyntaxToken keyword, out SyntaxNode type, out SyntaxNode? argumentList, out SyntaxNode? initializer) { var objectCreationExpression = (ObjectCreationExpressionSyntax)node; + keyword = objectCreationExpression.NewKeyword; type = objectCreationExpression.Type; argumentList = objectCreationExpression.ArgumentList; initializer = objectCreationExpression.Initializer; } + public void GetPartsOfImplicitObjectCreationExpression(SyntaxNode node, out SyntaxToken keyword, out SyntaxNode argumentList, out SyntaxNode? initializer) + { + var implicitObjectCreationExpression = (ImplicitObjectCreationExpressionSyntax)node; + keyword = implicitObjectCreationExpression.NewKeyword; + argumentList = implicitObjectCreationExpression.ArgumentList; + initializer = implicitObjectCreationExpression.Initializer; + } + public void GetPartsOfParameter(SyntaxNode node, out SyntaxToken identifier, out SyntaxNode? @default) { var parameter = (ParameterSyntax)node; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/SpeculationAnalyzer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/SpeculationAnalyzer.cs index de81fa58d62d6..3c8f9c36452cc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/SpeculationAnalyzer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Utilities/SpeculationAnalyzer.cs @@ -769,7 +769,7 @@ bool IsSupportedConstructWithNullType() return true; } - // Similar to above, it's fine for a collection expression to have a a 'null' direct type (as long as a + // Similar to above, it's fine for a collection expression to have a 'null' direct type (as long as a // target-typed collection-expression conversion happened). Note: unlike above, we don't have to check // a language version since collection expressions always supported collection-expression-conversions. if (newExpression.IsKind(SyntaxKind.CollectionExpression) && @@ -778,6 +778,16 @@ bool IsSupportedConstructWithNullType() return true; } + // Similar to above, it's fine for a tuple expression to have a 'null' direct type (as long as a + // target-typed tuple-expression conversion happened). Note: unlike above, we don't have to check + // a language version since tuple expressions always supported tuple-expression-conversions. + if (newExpression.IsKind(SyntaxKind.TupleExpression) && + this.SpeculativeSemanticModel.GetConversion(newExpression).IsTupleLiteralConversion && + SymbolsAreCompatible(originalTypeInfo.Type, newTypeInfo.ConvertedType)) + { + return true; + } + return false; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs index ae38099441407..0e0c3b9ca927a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Collections; @@ -164,15 +165,14 @@ private int FillWithIntervalsThatMatch( return 0; } - var candidates = s_stackPool.Allocate(); + using var pooledObject = s_stackPool.GetPooledObject(); + var candidates = pooledObject.Object; var matches = FillWithIntervalsThatMatch( start, length, testInterval, ref builder, in introspector, stopAfterFirst, candidates); - s_stackPool.ClearAndFree(candidates); - return matches; } @@ -188,9 +188,8 @@ private int FillWithIntervalsThatMatch( candidates.Push((root, firstTime: true)); - while (candidates.Count > 0) + while (candidates.TryPop(out var currentTuple)) { - var currentTuple = candidates.Pop(); var currentNode = currentTuple.node; RoslynDebug.Assert(currentNode != null); @@ -322,11 +321,11 @@ public IEnumerator GetEnumerator() yield break; } - var candidates = new Stack<(Node? node, bool firstTime)>(); + using var _ = ArrayBuilder<(Node? node, bool firstTime)>.GetInstance(out var candidates); candidates.Push((root, firstTime: true)); - while (candidates.Count != 0) + while (candidates.TryPop(out var tuple)) { - var (currentNode, firstTime) = candidates.Pop(); + var (currentNode, firstTime) = tuple; if (currentNode != null) { if (firstTime) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index bbe77051ecf65..87ccfaddde658 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -497,6 +497,7 @@ + @@ -532,7 +533,6 @@ - diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/StructuredAnalyzerConfigOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/StructuredAnalyzerConfigOptions.cs index 76b0896a974b6..359c76a42da31 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/StructuredAnalyzerConfigOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/StructuredAnalyzerConfigOptions.cs @@ -53,7 +53,7 @@ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? val } public override IEnumerable Keys - => SpecializedCollections.EmptyEnumerable(); + => []; } public static readonly StructuredAnalyzerConfigOptions Empty = new EmptyImplementation(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ICollectionExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ICollectionExtensions.cs index dc41d61adbb39..b0d57c834df5b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ICollectionExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ICollectionExtensions.cs @@ -21,7 +21,7 @@ public static ImmutableArray WhereAsArray(this IEnumerable valu result.Add(value); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public static void RemoveRange(this ICollection collection, IEnumerable? items) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs index 90d81d94307b5..da3c86ea8f721 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs @@ -388,12 +388,12 @@ private static ImmutableArray GetInterfacesToImplement( // implement. By definition they must contain all the necessary methods. var baseType = classOrStructType.BaseType; var alreadyImplementedInterfaces = baseType == null || allowReimplementation - ? SpecializedCollections.EmptyEnumerable() + ? [] : baseType.AllInterfaces; cancellationToken.ThrowIfCancellationRequested(); interfacesToImplement.RemoveRange(alreadyImplementedInterfaces); - return interfacesToImplement.ToImmutableArray(); + return [.. interfacesToImplement]; } private static ImmutableArray GetUnimplementedMembers( @@ -560,7 +560,7 @@ public static ImmutableArray GetOverridableMembers( RemoveNonOverriddableMembers(result, containingType, cancellationToken); } - return result.Keys.OrderBy(s => result[s]).ToImmutableArray(); + return [.. result.Keys.OrderBy(s => result[s])]; static void RemoveOverriddenMembers( Dictionary result, INamedTypeSymbol containingType, CancellationToken cancellationToken) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamespaceOrTypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamespaceOrTypeSymbolExtensions.cs index fdb6279f5eafe..169623435a5d6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamespaceOrTypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamespaceOrTypeSymbolExtensions.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Extensions; @@ -24,7 +25,7 @@ public static string GetShortName(this INamespaceOrTypeSymbol symbol) public static IEnumerable GetIndexers(this INamespaceOrTypeSymbol? symbol) { return symbol == null - ? SpecializedCollections.EmptyEnumerable() + ? [] : symbol.GetMembers(WellKnownMemberNames.Indexer).OfType().Where(p => p.IsIndexer); } @@ -89,21 +90,20 @@ public static IEnumerable GetAllTypes( this INamespaceOrTypeSymbol namespaceOrTypeSymbol, CancellationToken cancellationToken) { - var stack = new Stack(); + using var _ = ArrayBuilder.GetInstance(out var stack); stack.Push(namespaceOrTypeSymbol); - while (stack.Count > 0) + while (stack.TryPop(out var current)) { cancellationToken.ThrowIfCancellationRequested(); - var current = stack.Pop(); if (current is INamespaceSymbol currentNs) { - stack.Push(currentNs.GetMembers()); + stack.AddRange(currentNs.GetMembers()); } else { var namedType = (INamedTypeSymbol)current; - stack.Push(namedType.GetTypeMembers()); + stack.AddRange(namedType.GetTypeMembers()); yield return namedType; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ITypeSymbolExtensions.cs index 65bc4bcc48912..8732bd70f0b9c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ITypeSymbolExtensions.cs @@ -434,9 +434,7 @@ public static bool CanSupportCollectionInitializer(this ITypeSymbol typeSymbol, public static IEnumerable GetAccessibleMembersInBaseTypes(this ITypeSymbol containingType, ISymbol within) where T : class, ISymbol { if (containingType == null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var types = containingType.GetBaseTypes(); return types.SelectMany(x => x.GetMembers().OfType().Where(m => m.IsAccessibleWithin(within))); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs index dfdebdbf0f255..baa21e3959894 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs @@ -34,11 +34,11 @@ public static ConcatImmutableArray ConcatFast(this ImmutableArray first public static ImmutableArray TakeAsArray(this ImmutableArray array, int count) { - using var _ = ArrayBuilder.GetInstance(count, out var result); + var result = new FixedSizeArrayBuilder(count); for (var i = 0; i < count; i++) result.Add(array[i]); - return result.ToImmutableAndClear(); + return result.MoveToImmutable(); } public static ImmutableArray ToImmutableAndClear(this ImmutableArray.Builder builder) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ObjectWriterExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ObjectWriterExtensions.cs index d696816428312..cbc86569e6d57 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ObjectWriterExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ObjectWriterExtensions.cs @@ -27,11 +27,11 @@ public static ImmutableArray ReadArray(this ObjectReader reader, Func ReadArray(this ObjectReader reader, Func read, TArg arg) { var length = reader.ReadInt32(); - using var _ = ArrayBuilder.GetInstance(length, out var builder); + var builder = new FixedSizeArrayBuilder(length); for (var i = 0; i < length; i++) builder.Add(read(reader, arg)); - return builder.ToImmutableAndClear(); + return builder.MoveToImmutable(); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/StackExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/StackExtensions.cs index c8a84f45a472c..0df5a82729fd4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/StackExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/StackExtensions.cs @@ -4,12 +4,27 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Extensions; internal static class StackExtensions { +#if !NET + public static bool TryPop(this Stack stack, [MaybeNullWhen(false)] out T result) + { + if (stack.Count == 0) + { + result = default; + return false; + } + + result = stack.Pop(); + return true; + } +#endif + public static void Push(this Stack stack, IEnumerable values) { foreach (var v in values) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeExtensions.cs index f5e77e45790ef..782139a28ab85 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeExtensions.cs @@ -913,9 +913,8 @@ void FinishIf(TDirectiveTriviaSyntax? directive) if (directive != null) condDirectivesBuilder.Add(directive); - while (ifStack.Count > 0) + while (ifStack.TryPop(out var poppedDirective)) { - var poppedDirective = ifStack.Pop(); condDirectivesBuilder.Add(poppedDirective); if (poppedDirective.RawKind == syntaxKinds.IfDirectiveTrivia) break; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeOrTokenExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeOrTokenExtensions.cs index 9771fc5b08c44..bb0878db8dd8e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeOrTokenExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeOrTokenExtensions.cs @@ -16,10 +16,8 @@ public static IEnumerable DepthFirstTraversal(this SyntaxNode var stack = pooledStack.Object; stack.Push(node); - while (!stack.IsEmpty()) + while (stack.TryPop(out var current)) { - var current = stack.Pop(); - yield return current; if (current.IsNode) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenExtensions.cs index 976a89f97a2f7..3452fa0214efe 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenExtensions.cs @@ -26,14 +26,14 @@ public static IEnumerable GetAncestors(this SyntaxToken token) { return token.Parent != null ? token.Parent.AncestorsAndSelf().OfType() - : SpecializedCollections.EmptyEnumerable(); + : []; } public static IEnumerable GetAncestors(this SyntaxToken token, Func predicate) { return token.Parent != null ? token.Parent.AncestorsAndSelf().Where(predicate) - : SpecializedCollections.EmptyEnumerable(); + : []; } public static SyntaxNode? GetCommonRoot(this SyntaxToken token1, SyntaxToken token2) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenListExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenListExtensions.cs deleted file mode 100644 index 13f8933d4a916..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenListExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using Microsoft.CodeAnalysis.PooledObjects; - -namespace Microsoft.CodeAnalysis.Shared.Extensions; - -internal static class SyntaxTokenListExtensions -{ - public static SyntaxTokenList ToSyntaxTokenList(this IEnumerable tokens) - => new(tokens); - - public static SyntaxTokenList ToSyntaxTokenListAndFree(this ArrayBuilder tokens) - { - var tokenList = new SyntaxTokenList(tokens); - tokens.Free(); - return tokenList; - } -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs index c5cf608238719..fa6a76cf05a04 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs @@ -86,7 +86,7 @@ protected AnalysisData() /// public SymbolUsageResult ToResult() => new(SymbolsWriteBuilder.ToImmutableDictionary(), - SymbolsReadBuilder.ToImmutableHashSet()); + [.. SymbolsReadBuilder]); public BasicBlockAnalysisData AnalyzeLocalFunctionInvocation(IMethodSymbol localFunction, CancellationToken cancellationToken) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs index 1a25bc7db5577..8b0b6e1b3b97a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs @@ -196,7 +196,7 @@ private static ImmutableHashSet GetCapturedLocals(ControlFlowGraph } } - return builder.ToImmutableHashSet(); + return [.. builder]; } public BasicBlockAnalysisData GetBlockAnalysisData(BasicBlock basicBlock) @@ -600,7 +600,7 @@ public override bool TryGetDelegateInvocationTargets(IOperation write, out Immut // Attempts to return potential lamba/local function delegate invocation targets for the given write. if (_reachingDelegateCreationTargets.TryGetValue(write, out var targetsBuilder)) { - targets = targetsBuilder.ToImmutableHashSet(); + targets = [.. targetsBuilder]; return true; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/AbstractSyntaxFormatting.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/AbstractSyntaxFormatting.cs index d402fae0e432c..662e591c5012e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/AbstractSyntaxFormatting.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/AbstractSyntaxFormatting.cs @@ -41,9 +41,7 @@ public IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable() - : SpecializedCollections.SingletonReadOnlyList(node.FullSpan); + spansToFormat = node.FullSpan.IsEmpty ? [] : [node.FullSpan]; } else { @@ -52,7 +50,7 @@ public IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable()); + return CreateAggregatedFormattingResult(node, results: []); } rules ??= GetDefaultFormattingRules(); @@ -72,7 +70,7 @@ public IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable()); + return CreateAggregatedFormattingResult(node, results: []); } if (results.Count == 1) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ContextIntervalTree.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ContextIntervalTree.cs index c433b6e729337..94098697587b5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ContextIntervalTree.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ContextIntervalTree.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Microsoft.CodeAnalysis.Shared.Collections; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Formatting; @@ -111,10 +112,8 @@ private bool ContainsEdgeInclusive(T value, int start, int length) // we reached the point, where we can't go down anymore. // now, go back up to find best answer - while (spineNodes.Count > 0) + while (spineNodes.TryPop(out currentNode)) { - currentNode = spineNodes.Pop(); - // check whether current node meets condition if (predicate(currentNode.Value, start, length)) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractTriviaDataFactory.FormattedWhitespace.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractTriviaDataFactory.FormattedWhitespace.cs index 71f119bb85efa..82ae7747c65b3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractTriviaDataFactory.FormattedWhitespace.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractTriviaDataFactory.FormattedWhitespace.cs @@ -51,7 +51,7 @@ private string CreateString(string newLine) public override bool ContainsChanges => true; public override IEnumerable GetTextChanges(TextSpan textSpan) - => SpecializedCollections.SingletonEnumerable(new TextChange(textSpan, _newString)); + => [new TextChange(textSpan, _newString)]; public override TriviaData WithSpace(int space, FormattingContext context, ChainedFormattingRules formattingRules) => throw new NotImplementedException(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs index 0ecff004749f5..8073cdc2c43eb 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs @@ -34,34 +34,17 @@ public static SyntaxNode GetParentWithBiggerSpan(this SyntaxNode node) } public static IEnumerable Concat(this AbstractFormattingRule rule, IEnumerable rules) - => SpecializedCollections.SingletonEnumerable(rule).Concat(rules); - - public static void AddRange(this IList list, IEnumerable values) - { - foreach (var v in values) - { - list.Add(v); - } - } + => [rule, .. rules]; [return: NotNullIfNotNull(nameof(list1)), NotNullIfNotNull(nameof(list2))] public static List? Combine(this List? list1, List? list2) - { - if (list1 == null) + => (list1, list2) switch { - return list2; - } - else if (list2 == null) - { - return list1; - } - - // normal case - var combinedList = new List(list1); - combinedList.AddRange(list2); - - return combinedList; - } + (null, _) => list2, + (_, null) => list1, + // normal case + _ => [.. list1, .. list2] + }; public static bool ContainsElasticTrivia(this SuppressOperation operation, TokenStream tokenStream) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/AbstractTriviaFormatter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/AbstractTriviaFormatter.cs index 928d8b88e75c4..d1723001a4b91 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/AbstractTriviaFormatter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/AbstractTriviaFormatter.cs @@ -275,7 +275,7 @@ public ImmutableArray FormatToTextChanges(CancellationToken cancella if (Succeeded()) { - return changes.ToImmutable(); + return changes.ToImmutableAndClear(); } return []; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index 77d2d04e8bddc..b99371005305b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -622,6 +622,14 @@ internal enum FunctionId RemoteWorkspace_SolutionCachingStatistics = 750, + SemanticSearch_QueryExecution = 760, + // 800-850 for Copilot performance logging. Copilot_Suggestion_Dismissed = 800, + Copilot_On_The_Fly_Docs_Showed_Link = 810, + Copilot_On_The_Fly_Docs_Loading_State_Entered = 811, + Copilot_On_The_Fly_Docs_Results_Displayed = 812, + Copilot_On_The_Fly_Docs_Error_Displayed = 813, + Copilot_On_The_Fly_Docs_Results_Canceled = 814, + Copilot_Rename = 851 } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs index 8a16e17022ee6..4914bffaf8760 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs @@ -50,6 +50,13 @@ public static PooledObject> GetPooledObject(this ObjectPool> GetPooledObject(this ObjectPool> pool, out HashSet list) + { + var pooledObject = PooledObject>.Create(pool); + list = pooledObject.Object; + return pooledObject; + } + public static PooledObject GetPooledObject(this ObjectPool pool) where T : class => new(pool, p => p.Allocate(), (p, o) => p.Free(o)); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/InternalOptionStorageMapping.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/InternalOptionStorageMapping.cs index 801e96425ac41..c2beed2920b24 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/InternalOptionStorageMapping.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/InternalOptionStorageMapping.cs @@ -16,7 +16,7 @@ internal abstract class OptionStorageMapping(IOption2 internalOption) public IOption2 InternalOption { get; } = internalOption; /// - /// Converts inernal option value representation to public. + /// Converts internal option value representation to public. /// public abstract object? ToPublicOptionValue(object? internalValue); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs index 2eabf6a5a4797..bf40076c353dc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs @@ -7,7 +7,6 @@ using System.IO; using System.Runtime.ExceptionServices; using System.Text; -using System.Threading; using Microsoft.CodeAnalysis; namespace Roslyn.Utilities; @@ -28,7 +27,6 @@ internal sealed partial class ObjectReader : IDisposable internal const byte VersionByte2 = 0b00001101; private readonly BinaryReader _reader; - private readonly CancellationToken _cancellationToken; /// /// Map of reference id's to deserialized strings. @@ -40,11 +38,7 @@ internal sealed partial class ObjectReader : IDisposable /// /// The stream to read objects from. /// True to leave the open after the is disposed. - /// - private ObjectReader( - Stream stream, - bool leaveOpen, - CancellationToken cancellationToken) + private ObjectReader(Stream stream, bool leaveOpen) { // String serialization assumes both reader and writer to be of the same endianness. // It can be adjusted for BigEndian if needed. @@ -52,8 +46,6 @@ private ObjectReader( _reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen); _stringReferenceMap = ReaderReferenceMap.Create(); - - _cancellationToken = cancellationToken; } /// @@ -61,10 +53,7 @@ private ObjectReader( /// If the does not start with a valid header, then will /// be returned. /// - public static ObjectReader? TryGetReader( - Stream? stream, - bool leaveOpen = false, - CancellationToken cancellationToken = default) + public static ObjectReader? TryGetReader(Stream? stream, bool leaveOpen = false) { if (stream == null) { @@ -91,43 +80,42 @@ private ObjectReader( #endif } - return new ObjectReader(stream, leaveOpen, cancellationToken); + return new ObjectReader(stream, leaveOpen); } - /// - /// Creates an from the provided . - /// Unlike , it requires the version - /// of the data in the stream to exactly match the current format version. - /// Should only be used to read data written by the same version of Roslyn. + /// + /// Creates an from the provided . Unlike , it requires the version of the data in the stream to + /// exactly match the current format version. Should only be used to read data written by the same version of + /// Roslyn. /// - public static ObjectReader GetReader( - Stream stream, - bool leaveOpen, - CancellationToken cancellationToken) + public static ObjectReader GetReader(Stream stream, bool leaveOpen) + => GetReader(stream, leaveOpen, checkValidationBytes: true); + + /// + /// + /// Whether or not the validation bytes (see should be checked immediately at the stream's current + /// position. + /// + public static ObjectReader GetReader(Stream stream, bool leaveOpen, bool checkValidationBytes) { - var b = stream.ReadByte(); - if (b == -1) - { - throw new EndOfStreamException(); - } + var reader = new ObjectReader(stream, leaveOpen); + if (checkValidationBytes) + reader.CheckValidationBytes(); + + return reader; + } + public void CheckValidationBytes() + { + var b = this.ReadByte(); if (b != VersionByte1) - { throw ExceptionUtilities.UnexpectedValue(b); - } - - b = stream.ReadByte(); - if (b == -1) - { - throw new EndOfStreamException(); - } + b = this.ReadByte(); if (b != VersionByte2) - { throw ExceptionUtilities.UnexpectedValue(b); - } - - return new ObjectReader(stream, leaveOpen, cancellationToken); } public void Dispose() diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs index 1aa1eb0bfdd2a..fd03f06cfe9f2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs @@ -8,7 +8,6 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; -using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; using EncodingExtensions = Microsoft.CodeAnalysis.EncodingExtensions; @@ -53,7 +52,6 @@ private static class BufferPool public const byte Byte4Marker = 2 << 6; private readonly BinaryWriter _writer; - private readonly CancellationToken _cancellationToken; /// /// Map of serialized string reference ids. The string-reference-map uses value-equality for greater cache hits @@ -81,11 +79,15 @@ private static class BufferPool /// /// The stream to write to. /// True to leave the open after the is disposed. - /// Cancellation token. - public ObjectWriter( - Stream stream, - bool leaveOpen = false, - CancellationToken cancellationToken = default) + public ObjectWriter(Stream stream, bool leaveOpen = false) + : this(stream, leaveOpen, writeValidationBytes: true) + { + } + + /// + /// Whether or not the validation bytes (see ) + /// should be immediately written into the stream. + public ObjectWriter(Stream stream, bool leaveOpen, bool writeValidationBytes) { // String serialization assumes both reader and writer to be of the same endianness. // It can be adjusted for BigEndian if needed. @@ -93,12 +95,17 @@ public ObjectWriter( _writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen); _stringReferenceMap = new WriterReferenceMap(); - _cancellationToken = cancellationToken; - WriteVersion(); + if (writeValidationBytes) + WriteValidationBytes(); } - private void WriteVersion() + /// + /// Writes out a special sequence of bytes indicating that the stream is a serialized object stream. Used by the + /// to be able to easily detect if it is being improperly used, or if the stream is + /// corrupt. + /// + public void WriteValidationBytes() { WriteByte(ObjectReader.VersionByte1); WriteByte(ObjectReader.VersionByte2); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs index 99aeca8efb44c..4bf5a87ee261d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs @@ -157,7 +157,7 @@ public TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives GetMembersInSpan( AddSelectedMemberDeclarations(member, membersToKeep); } - return selectedMembers.ToImmutable(); + return selectedMembers.ToImmutableAndClear(); void AddAllMembers(TMemberDeclarationSyntax member) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/AbstractDocumentationCommentService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/AbstractDocumentationCommentService.cs index 8234264c462f6..6c75e926003b1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/AbstractDocumentationCommentService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/AbstractDocumentationCommentService.cs @@ -185,7 +185,7 @@ private static bool HasLeadingWhitespace(string tokenText) => tokenText.Length > 0 && char.IsWhiteSpace(tokenText[0]); private static bool HasTrailingWhitespace(string tokenText) - => tokenText.Length > 0 && char.IsWhiteSpace(tokenText[tokenText.Length - 1]); + => tokenText.Length > 0 && char.IsWhiteSpace(tokenText[^1]); public string GetBannerText(SyntaxNode documentationCommentTriviaSyntax, int maxBannerLength, CancellationToken cancellationToken) => GetBannerText((TDocumentationCommentTriviaSyntax)documentationCommentTriviaSyntax, maxBannerLength, cancellationToken); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index 969b8f3878681..3fc36df6dda1b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -528,7 +528,8 @@ void GetPartsOfTupleExpression(SyntaxNode node, void GetPartsOfIsPatternExpression(SyntaxNode node, out SyntaxNode left, out SyntaxToken isToken, out SyntaxNode right); void GetPartsOfMemberAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxToken operatorToken, out SyntaxNode name); void GetPartsOfNamedMemberInitializer(SyntaxNode node, out SyntaxNode name, out SyntaxNode expression); - void GetPartsOfObjectCreationExpression(SyntaxNode node, out SyntaxNode type, out SyntaxNode? argumentList, out SyntaxNode? initializer); + void GetPartsOfObjectCreationExpression(SyntaxNode node, out SyntaxToken keyword, out SyntaxNode type, out SyntaxNode? argumentList, out SyntaxNode? initializer); + void GetPartsOfImplicitObjectCreationExpression(SyntaxNode node, out SyntaxToken keyword, out SyntaxNode argumentList, out SyntaxNode? initializer); void GetPartsOfParameter(SyntaxNode node, out SyntaxToken identifier, out SyntaxNode? @default); void GetPartsOfParenthesizedExpression(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode expression, out SyntaxToken closeParen); void GetPartsOfPrefixUnaryExpression(SyntaxNode node, out SyntaxToken operatorToken, out SyntaxNode operand); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs index 29deeaeb60fc6..04668927a3f0c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs @@ -36,22 +36,21 @@ public static bool IsOnSingleLine(this ISyntaxFacts syntaxFacts, SyntaxNode node // and all the trivia on each token. If full-span is false we'll examine all tokens // but we'll ignore the leading trivia on the very first trivia and the trailing trivia // on the very last token. - var stack = s_stackPool.Allocate(); + using var pooledObject = s_stackPool.GetPooledObject(); + var stack = pooledObject.Object; stack.Push((node, leading: fullSpan, trailing: fullSpan)); var result = IsOnSingleLine(syntaxFacts, stack); - s_stackPool.ClearAndFree(stack); - return result; } private static bool IsOnSingleLine( ISyntaxFacts syntaxFacts, Stack<(SyntaxNodeOrToken nodeOrToken, bool leading, bool trailing)> stack) { - while (stack.Count > 0) + while (stack.TryPop(out var tuple)) { - var (currentNodeOrToken, currentLeading, currentTrailing) = stack.Pop(); + var (currentNodeOrToken, currentLeading, currentTrailing) = tuple; if (currentNodeOrToken.IsToken) { // If this token isn't on a single line, then the original node definitely @@ -383,7 +382,7 @@ private static bool IsWordOrNumber(this ISyntaxFacts syntaxFacts, SyntaxToken to => syntaxFacts.IsWord(token) || syntaxFacts.IsNumericLiteral(token); public static bool SpansPreprocessorDirective(this ISyntaxFacts service, SyntaxNode node) - => service.SpansPreprocessorDirective(SpecializedCollections.SingletonEnumerable(node)); + => service.SpansPreprocessorDirective([node]); public static bool SpansPreprocessorDirective(this ISyntaxFacts service, params SyntaxNode[] nodes) => service.SpansPreprocessorDirective((IEnumerable)nodes); @@ -595,7 +594,7 @@ public static SeparatedSyntaxList GetTypeArgumentsOfGenericName(this public static SyntaxNode GetTypeOfObjectCreationExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node) { - syntaxFacts.GetPartsOfObjectCreationExpression(node, out var type, out _, out _); + syntaxFacts.GetPartsOfObjectCreationExpression(node, out _, out var type, out _, out _); return type; } @@ -648,7 +647,7 @@ public static bool IsTypeOfObjectCreationExpression(this ISyntaxFacts syntaxFact if (!syntaxFacts.IsObjectCreationExpression(parent)) return false; - syntaxFacts.GetPartsOfObjectCreationExpression(parent, out var type, out _, out _); + syntaxFacts.GetPartsOfObjectCreationExpression(parent, out _, out var type, out _, out _); return type == node; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.BodyLevelSymbolKey.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.BodyLevelSymbolKey.cs index 4fd9c43b720b8..80aa37a6a9439 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.BodyLevelSymbolKey.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.BodyLevelSymbolKey.cs @@ -33,7 +33,7 @@ public static ImmutableArray GetBodyLevelSourceLocations(ISymbol symbo foreach (var syntaxRef in symbol.DeclaringSyntaxReferences) result.Add(syntaxRef.GetSyntax(cancellationToken).GetLocation()); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public static void Create(ISymbol symbol, SymbolKeyWriter visitor) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.ErrorTypeSymbolKey.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.ErrorTypeSymbolKey.cs index d37f8bd61d44f..319e43c9e32e0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.ErrorTypeSymbolKey.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.ErrorTypeSymbolKey.cs @@ -59,7 +59,7 @@ private static ImmutableArray GetContainingNamespaceNamesInReverse(IName namespaceSymbol = namespaceSymbol.ContainingNamespace; } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } protected sealed override SymbolKeyResolution Resolve( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.SymbolKeyWriter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.SymbolKeyWriter.cs index 372759c1b67c3..fa33b1cebae1c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.SymbolKeyWriter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.SymbolKeyWriter.cs @@ -516,7 +516,7 @@ public void PushMethod(IMethodSymbol method) public void PopMethod(IMethodSymbol method) { Contract.ThrowIfTrue(_methodSymbolStack.Count == 0); - Contract.ThrowIfFalse(method.Equals(_methodSymbolStack[_methodSymbolStack.Count - 1])); + Contract.ThrowIfFalse(method.Equals(_methodSymbolStack[^1])); _methodSymbolStack.RemoveAt(_methodSymbolStack.Count - 1); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs index 331ca8a3c5653..db14883bd1652 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Runtime.CompilerServices; namespace Roslyn.Utilities; @@ -22,11 +23,11 @@ internal static partial class Contract /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value, [CallerLineNumber] int lineNumber = 0) where T : class? + public static void ThrowIfNull([NotNull] T value, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) where T : class? { if (value is null) { - Fail("Unexpected null", lineNumber); + Fail("Unexpected null", lineNumber, filePath); } } @@ -35,11 +36,11 @@ public static void ThrowIfNull([NotNull] T value, [CallerLineNumber] int line /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T? value, [CallerLineNumber] int lineNumber = 0) where T : struct + public static void ThrowIfNull([NotNull] T? value, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) where T : struct { if (value is null) { - Fail("Unexpected null", lineNumber); + Fail("Unexpected null", lineNumber, filePath); } } @@ -48,11 +49,11 @@ public static void ThrowIfNull([NotNull] T? value, [CallerLineNumber] int lin /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value, string message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfNull([NotNull] T value, string message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (value is null) { - Fail(message, lineNumber); + Fail(message, lineNumber, filePath); } } @@ -61,11 +62,11 @@ public static void ThrowIfNull([NotNull] T value, string message, [CallerLine /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value, [InterpolatedStringHandlerArgument("value")] ThrowIfNullInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfNull([NotNull] T value, [InterpolatedStringHandlerArgument("value")] ThrowIfNullInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (value is null) { - Fail(message.GetFormattedText(), lineNumber); + Fail(message.GetFormattedText(), lineNumber, filePath); } } @@ -74,11 +75,11 @@ public static void ThrowIfNull([NotNull] T value, [InterpolatedStringHandlerA /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (!condition) { - Fail("Unexpected false", lineNumber); + Fail("Unexpected false", lineNumber, filePath); } } @@ -87,11 +88,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (!condition) { - Fail(message, lineNumber); + Fail(message, lineNumber, filePath); } } @@ -100,11 +101,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfFalseInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfFalseInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (!condition) { - Fail(message.GetFormattedText(), lineNumber); + Fail(message.GetFormattedText(), lineNumber, filePath); } } @@ -113,11 +114,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (condition) { - Fail("Unexpected true", lineNumber); + Fail("Unexpected true", lineNumber, filePath); } } @@ -126,11 +127,11 @@ public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool cond /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, string message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, string message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (condition) { - Fail(message, lineNumber); + Fail(message, lineNumber, filePath); } } @@ -139,17 +140,20 @@ public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool cond /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfTrueInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfTrueInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (condition) { - Fail(message.GetFormattedText(), lineNumber); + Fail(message.GetFormattedText(), lineNumber, filePath); } } [DebuggerHidden] [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void Fail(string message = "Unexpected", [CallerLineNumber] int lineNumber = 0) - => throw new InvalidOperationException($"{message} - line {lineNumber}"); + public static void Fail(string message = "Unexpected", [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) + { + var fileName = filePath is null ? null : Path.GetFileName(filePath); + throw new InvalidOperationException($"{message} - file {fileName} line {lineNumber}"); + } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EditDistance.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EditDistance.cs index 086cb0e922331..e32225e85e0a0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EditDistance.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EditDistance.cs @@ -178,7 +178,7 @@ private static int GetEditDistanceWorker(ReadOnlySpan source, ReadOnlySpan // First: // Determine the common prefix/suffix portions of the strings. We don't even need to // consider them as they won't add anything to the edit cost. - while (source.Length > 0 && source[source.Length - 1] == target[target.Length - 1]) + while (source.Length > 0 && source[^1] == target[^1]) { source = source[..^1]; target = target[..^1]; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs new file mode 100644 index 0000000000000..1a0ade6ecf363 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.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.Collections.Generic; +using System.Collections.Immutable; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +/// +/// A bare-bones array builder, focused on the case of producing s where the final array +/// size is known at construction time. In the golden path, where all the expected items are added to the builder, and +/// is called, this type is entirely garbage free. In the non-golden path (usually +/// encountered when a cancellation token interrupts getting the final array), this will leak the intermediary array +/// created to store the results. +/// +/// +/// This type should only be used when all of the following are true: +/// +/// +/// The number of elements is known up front, and is fixed. In other words, it isn't just an initial-capacity, or a +/// rough heuristic. Rather it will always be the exact number of elements added. +/// +/// +/// Exactly that number of elements is actually added prior to calling . This means no +/// patterns like "AddIfNotNull". +/// +/// +/// The builder will be moved to an array (see ) or (see ). +/// +/// +/// If any of the above are not true (for example, the capacity is a rough hint, or the exact number of elements may not +/// match the capacity specified, or if it's intended as a scratch buffer, and won't realize a final array), then should be used instead. +/// +[NonCopyable] +internal struct FixedSizeArrayBuilder(int capacity) +{ + private T[] _values = new T[capacity]; + private int _index; + + public void Add(T value) + => _values[_index++] = value; + + #region AddRange overloads. These allow us to add these collections directly, without allocating an enumerator. + + public void AddRange(ImmutableArray values) + { + Contract.ThrowIfTrue(_index + values.Length > _values.Length); + Array.Copy(ImmutableCollectionsMarshal.AsArray(values)!, 0, _values, _index, values.Length); + _index += values.Length; + } + + public void AddRange(List values) + { + Contract.ThrowIfTrue(_index + values.Count > _values.Length); + foreach (var v in values) + Add(v); + } + + public void AddRange(HashSet values) + { + Contract.ThrowIfTrue(_index + values.Count > _values.Length); + foreach (var v in values) + Add(v); + } + + public void AddRange(ArrayBuilder values) + { + Contract.ThrowIfTrue(_index + values.Count > _values.Length); + foreach (var v in values) + Add(v); + } + + #endregion + + public void AddRange(IEnumerable values) + { + foreach (var v in values) + Add(v); + } + + public readonly void Sort() + => Sort(Comparer.Default); + + public readonly void Sort(IComparer comparer) + { + if (_index > 1) + Array.Sort(_values, 0, _index, comparer); + } + + /// + /// Moves the underlying buffer out of control of this type, into the returned . It + /// is an error for a client of this type to specify a capacity and then attempt to call without that number of elements actually having been added to the builder. This will + /// throw if attempted. This is effectively unusable once this is called. + /// The internal buffer will reset to an empty array, meaning no more items could ever be added to it. + /// + public ImmutableArray MoveToImmutable() + => ImmutableCollectionsMarshal.AsImmutableArray(MoveToArray()); + + public T[] MoveToArray() + { + Contract.ThrowIfTrue(_index != _values.Length); + var result = _values; + _values = Array.Empty(); + _index = 0; + return result; + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ICollectionExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ICollectionExtensions.cs index e8aacbb4b9ca7..f864393c6a3cb 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ICollectionExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ICollectionExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using Microsoft.CodeAnalysis.PooledObjects; namespace Roslyn.Utilities; @@ -26,6 +27,18 @@ public static void AddRange(this ICollection collection, IEnumerable? v } } + public static void AddRange(this ICollection collection, ArrayBuilder? values) + { + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + if (values != null) + { + foreach (var item in values) + collection.Add(item); + } + } + public static void AddRange(this ICollection collection, HashSet? values) { if (collection == null) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IReadOnlyDictionaryExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IReadOnlyDictionaryExtensions.cs index 1df6973fea720..a00213fe05cc4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IReadOnlyDictionaryExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IReadOnlyDictionaryExtensions.cs @@ -25,7 +25,7 @@ public static IEnumerable GetEnumerableMetadata(this IReadOnlyDictionary enumerable: return enumerable; case T s: return SpecializedCollections.SingletonEnumerable(s); - default: return SpecializedCollections.EmptyEnumerable(); + default: return []; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PathMetadataUtilities.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PathMetadataUtilities.cs index 07dbf18c1be31..5754d73ddfbc0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PathMetadataUtilities.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PathMetadataUtilities.cs @@ -71,6 +71,6 @@ public static ImmutableArray BuildFoldersFromNamespace(string? @namespac } var parts = @namespace.Split(NamespaceSeparatorArray, options: StringSplitOptions.RemoveEmptyEntries); - return parts.ToImmutableArray(); + return [.. parts]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs index 01042b6a8acc0..be373a34a7009 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/PooledBuilderExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -91,4 +92,33 @@ public static ImmutableArray ToFlattenedImmutableArrayAndFree(this ArrayBu builders.Free(); } } + + public static bool HasDuplicates(this ArrayBuilder builder) + => builder.HasDuplicates(static x => x); + + public static bool HasDuplicates(this ArrayBuilder builder, Func selector) + { + switch (builder.Count) + { + case 0: + case 1: + return false; + + case 2: + return EqualityComparer.Default.Equals(selector(builder[0]), selector(builder[1])); + + default: + { + using var _ = PooledHashSet.GetInstance(out var set); + + foreach (var element in builder) + { + if (!set.Add(selector(element))) + return true; + } + + return false; + } + } + } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs index 9886c1975190c..83d4ad3474067 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs @@ -10,5 +10,5 @@ namespace System.Runtime.CompilerServices; internal sealed class RestrictedInternalsVisibleToAttribute(string assemblyName, params string[] allowedNamespaces) : Attribute { public string AssemblyName { get; } = assemblyName; - public ImmutableArray AllowedNamespaces { get; } = allowedNamespaces.ToImmutableArray(); + public ImmutableArray AllowedNamespaces { get; } = [.. allowedNamespaces]; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs index 4db53c7e33f27..b0e1e7f090ca1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs @@ -6,10 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -92,10 +89,10 @@ private static void BlowChunks(byte[][]? chunks) } } - internal static PooledStream CreateWritableStream() - => new ReadWriteStream(); + internal static ReadWriteStream CreateWritableStream() + => new(); - public class PooledStream : Stream + public abstract class PooledStream : Stream { protected List chunks; @@ -109,13 +106,7 @@ protected PooledStream(long length, List chunks) this.chunks = chunks; } - public override long Length - { - get - { - return this.length; - } - } + public override long Length => this.length; public override bool CanRead => true; @@ -130,10 +121,7 @@ public override void Flush() public override long Position { - get - { - return this.position; - } + get => this.position; set { @@ -176,16 +164,9 @@ public override long Seek(long offset, SeekOrigin origin) public override int ReadByte() { if (position >= length) - { return -1; - } - - var currentIndex = CurrentChunkIndex; - var chunk = chunks[currentIndex]; - - var currentOffset = CurrentChunkOffset; - var result = chunk[currentOffset]; + var result = chunks[CurrentChunkIndex][CurrentChunkOffset]; this.position++; return result; } @@ -295,7 +276,7 @@ public override void Write(byte[] buffer, int offset, int count) { } - private class ReadWriteStream : PooledStream + public sealed class ReadWriteStream : PooledStream { public ReadWriteStream() : base(length: 0, chunks: SharedPools.BigDefault>().AllocateAndClear()) @@ -337,13 +318,20 @@ private void EnsureCapacity(long value) } public override void SetLength(long value) + => SetLength(value, truncate: true); + + /// + /// Sets the length of this stream (see . If is , the internal buffers will be left as is, and the data in them will be left as garbage. + /// If it is then any fully unused chunks will be discarded. If there is a final chunk + /// the stream is partway through, the remainder of that chunk will be zeroed out. + /// + public void SetLength(long value, bool truncate) { EnsureCapacity(value); - if (value < length) + if (value < length && truncate) { - // truncate the stream - var chunkIndex = GetChunkIndex(value); var chunkOffset = GetChunkOffset(value); @@ -351,9 +339,7 @@ public override void SetLength(long value) var trimIndex = chunkIndex + 1; for (var i = trimIndex; i < chunks.Count; i++) - { SharedPools.ByteArray.Free(chunks[i]); - } chunks.RemoveRange(trimIndex, chunks.Count - trimIndex); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SpecializedTasks.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SpecializedTasks.cs index 5001534375671..7f850fd522b94 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SpecializedTasks.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SpecializedTasks.cs @@ -87,14 +87,13 @@ public static ValueTask WhenAll(IEnumerable> tasks) [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "Naming is modeled after Task.WhenAll.")] public static async ValueTask> WhenAll(this IReadOnlyCollection> tasks) { - using var _ = ArrayBuilder.GetInstance(tasks.Count, out var result); - // Explicit cast to IEnumerable so we call the overload that doesn't allocate an array as the result. await Task.WhenAll((IEnumerable)tasks).ConfigureAwait(false); + var result = new FixedSizeArrayBuilder(tasks.Count); foreach (var task in tasks) result.Add(await task.ConfigureAwait(false)); - return result.ToImmutableAndClear(); + return result.MoveToImmutable(); } /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.cs index 9508d7774ac9d..a8bb3f5bc68ae 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.cs @@ -70,8 +70,8 @@ public SymbolEquivalenceComparer( // There are only so many EquivalenceVisitors and GetHashCodeVisitors we can have. // Create them all up front. - using var _1 = ArrayBuilder.GetInstance(capacity: 4, out var equivalenceVisitors); - using var _2 = ArrayBuilder.GetInstance(capacity: 4, out var getHashCodeVisitors); + using var equivalenceVisitors = TemporaryArray.Empty; + using var getHashCodeVisitors = TemporaryArray.Empty; AddVisitors(compareMethodTypeParametersByIndex: true, objectAndDynamicCompareEqually: true); AddVisitors(compareMethodTypeParametersByIndex: true, objectAndDynamicCompareEqually: false); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index 0bb0d7c271698..a805b26656095 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -1895,13 +1895,18 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService expression = namedField.Expression End Sub - Public Sub GetPartsOfObjectCreationExpression(node As SyntaxNode, ByRef type As SyntaxNode, ByRef argumentList As SyntaxNode, ByRef initializer As SyntaxNode) Implements ISyntaxFacts.GetPartsOfObjectCreationExpression + Public Sub GetPartsOfObjectCreationExpression(node As SyntaxNode, ByRef keyword As SyntaxToken, ByRef type As SyntaxNode, ByRef argumentList As SyntaxNode, ByRef initializer As SyntaxNode) Implements ISyntaxFacts.GetPartsOfObjectCreationExpression Dim objectCreationExpression = DirectCast(node, ObjectCreationExpressionSyntax) + keyword = objectCreationExpression.NewKeyword type = objectCreationExpression.Type argumentList = objectCreationExpression.ArgumentList initializer = objectCreationExpression.Initializer End Sub + Public Sub GetPartsOfImplicitObjectCreationExpression(node As SyntaxNode, ByRef keyword As SyntaxToken, ByRef argumentList As SyntaxNode, ByRef initializer As SyntaxNode) Implements ISyntaxFacts.GetPartsOfImplicitObjectCreationExpression + Throw New InvalidOperationException(DoesNotExistInVBErrorMessage) + End Sub + Public Sub GetPartsOfParameter(node As SyntaxNode, ByRef identifier As SyntaxToken, ByRef [default] As SyntaxNode) Implements ISyntaxFacts.GetPartsOfParameter Dim parameter = DirectCast(node, ParameterSyntax) identifier = parameter.Identifier.Identifier diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/AttributeGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/AttributeGenerator.cs index c99bd4fada087..24d4b62d7382f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/AttributeGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/AttributeGenerator.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; @@ -15,6 +14,8 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static SyntaxFactory; + internal static class AttributeGenerator { public static SyntaxList GenerateAttributeLists( @@ -30,8 +31,8 @@ public static SyntaxList GenerateAttributeLists( .WhereNotNull().ToList(); return attributeNodes.Count == 0 ? default - : [SyntaxFactory.AttributeList( - target.HasValue ? SyntaxFactory.AttributeTargetSpecifier(target.Value) : null, + : [AttributeList( + target.HasValue ? AttributeTargetSpecifier(target.Value) : null, [.. attributeNodes])]; } else @@ -50,9 +51,9 @@ public static SyntaxList GenerateAttributeLists( var attributeSyntax = TryGenerateAttribute(attribute, info); return attributeSyntax == null ? null - : SyntaxFactory.AttributeList( + : AttributeList( target.HasValue - ? SyntaxFactory.AttributeTargetSpecifier(target.Value) + ? AttributeTargetSpecifier(target.Value) : null, [attributeSyntax]); } @@ -76,7 +77,7 @@ public static SyntaxList GenerateAttributeLists( var attributeArguments = GenerateAttributeArgumentList(info.Generator, attribute); return attribute.AttributeClass.GenerateTypeSyntax() is NameSyntax nameSyntax - ? SyntaxFactory.Attribute(nameSyntax, attributeArguments) + ? Attribute(nameSyntax, attributeArguments) : null; } @@ -87,13 +88,13 @@ public static SyntaxList GenerateAttributeLists( var arguments = new List(); arguments.AddRange(attribute.ConstructorArguments.Select(c => - SyntaxFactory.AttributeArgument(ExpressionGenerator.GenerateExpression(generator, c)))); + AttributeArgument(ExpressionGenerator.GenerateExpression(generator, c)))); arguments.AddRange(attribute.NamedArguments.Select(kvp => - SyntaxFactory.AttributeArgument( - SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(kvp.Key)), null, + AttributeArgument( + NameEquals(IdentifierName(kvp.Key)), null, ExpressionGenerator.GenerateExpression(generator, kvp.Value)))); - return SyntaxFactory.AttributeArgumentList([.. arguments]); + return AttributeArgumentList([.. arguments]); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationHelpers.cs index 37c86cd6a8d2d..75d972a22f057 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationHelpers.cs @@ -19,6 +19,9 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class CSharpCodeGenerationHelpers { public static TDeclarationSyntax ConditionallyAddFormattingAnnotationTo( @@ -44,24 +47,24 @@ internal static void AddAccessibilityModifiers( switch (accessibility) { case Accessibility.Public: - tokens.Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + tokens.Add(PublicKeyword); break; case Accessibility.Protected: - tokens.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + tokens.Add(ProtectedKeyword); break; case Accessibility.Private: - tokens.Add(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); + tokens.Add(PrivateKeyword); break; case Accessibility.ProtectedAndInternal: - tokens.Add(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); - tokens.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + tokens.Add(PrivateKeyword); + tokens.Add(ProtectedKeyword); break; case Accessibility.Internal: - tokens.Add(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); + tokens.Add(InternalKeyword); break; case Accessibility.ProtectedOrInternal: - tokens.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - tokens.Add(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); + tokens.Add(ProtectedKeyword); + tokens.Add(InternalKeyword); break; } } @@ -94,7 +97,7 @@ private static TypeDeclarationSyntax ReplaceUnterminatedConstructs(TypeDeclarati var text = t1.ToString(); if (!text.EndsWith(MultiLineCommentTerminator, StringComparison.Ordinal)) { - return SyntaxFactory.SyntaxTrivia(SyntaxKind.MultiLineCommentTrivia, text + MultiLineCommentTerminator); + return SyntaxTrivia(SyntaxKind.MultiLineCommentTrivia, text + MultiLineCommentTerminator); } } else if (t1.Kind() == SyntaxKind.SkippedTokensTrivia) @@ -115,10 +118,10 @@ private static SyntaxTrivia ReplaceUnterminatedConstructs(SyntaxTrivia skippedTo var tokens = syntax.Tokens; - var updatedTokens = SyntaxFactory.TokenList(tokens.Select(ReplaceUnterminatedConstruct)); + var updatedTokens = TokenList(tokens.Select(ReplaceUnterminatedConstruct)); var updatedSyntax = syntax.WithTokens(updatedTokens); - return SyntaxFactory.Trivia(updatedSyntax); + return Trivia(updatedSyntax); } private static SyntaxToken ReplaceUnterminatedConstruct(SyntaxToken token) @@ -129,7 +132,7 @@ private static SyntaxToken ReplaceUnterminatedConstruct(SyntaxToken token) if (tokenText.Length <= 2 || tokenText.Last() != '"') { tokenText += '"'; - return SyntaxFactory.Literal(token.LeadingTrivia, tokenText, token.ValueText, token.TrailingTrivia); + return Literal(token.LeadingTrivia, tokenText, token.ValueText, token.TrailingTrivia); } } else if (token.IsRegularStringLiteral()) @@ -138,7 +141,7 @@ private static SyntaxToken ReplaceUnterminatedConstruct(SyntaxToken token) if (tokenText.Length <= 1 || tokenText.Last() != '"') { tokenText += '"'; - return SyntaxFactory.Literal(token.LeadingTrivia, tokenText, token.ValueText, token.TrailingTrivia); + return Literal(token.LeadingTrivia, tokenText, token.ValueText, token.TrailingTrivia); } } @@ -182,7 +185,7 @@ public static SyntaxList Insert( if (index != 0 && declarationList[index - 1].ContainsDiagnostics && AreBracesMissing(declarationList[index - 1])) { - return declarationList.Insert(index, declaration.WithLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed)); + return declarationList.Insert(index, declaration.WithLeadingTrivia(ElasticCarriageReturnLineFeed)); } return declarationList.Insert(index, declaration); @@ -217,7 +220,7 @@ private static bool AreBracesMissing(TDeclaration declaration) whe return null; } - return SyntaxFactory.ExplicitInterfaceSpecifier(name); + return ExplicitInterfaceSpecifier(name); } public static CodeGenerationDestination GetDestination(SyntaxNode destination) @@ -253,8 +256,8 @@ public static TSyntaxNode ConditionallyAddDocumentationCommentTo( } var result = TryGetDocumentationComment(symbol, "///", out var comment, cancellationToken) - ? node.WithPrependedLeadingTrivia(SyntaxFactory.ParseLeadingTrivia(comment)) - .WithPrependedLeadingTrivia(SyntaxFactory.ElasticMarker) + ? node.WithPrependedLeadingTrivia(ParseLeadingTrivia(comment)) + .WithPrependedLeadingTrivia(ElasticMarker) : node; return result; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs index d3d2c3bfef690..8df5ceefaca7f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -23,6 +22,9 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal partial class CSharpCodeGenerationService : AbstractCodeGenerationService { public CSharpCodeGenerationService(LanguageServices languageServices) @@ -461,7 +463,7 @@ private static SyntaxList RemoveAttributeFromAttributeLists { // Remove just the given attribute from the attribute list. ComputePositionAndTriviaForRemoveAttributeFromAttributeList(attributeToRemove, (SyntaxToken t) => t.IsKind(SyntaxKind.CommaToken), out positionOfRemovedNode, out trivia); - var newAttributes = SyntaxFactory.SeparatedList(attributes.Where(a => a != attributeToRemove)); + var newAttributes = SeparatedList(attributes.Where(a => a != attributeToRemove)); var newAttributeList = attributeList.WithAttributes(newAttributes); newAttributeLists = attributeLists.Select(attrList => attrList == attributeList ? newAttributeList : attrList); } @@ -499,7 +501,7 @@ public override TDeclarationNode AddStatements( } else if (destinationMember is AccessorDeclarationSyntax accessorDeclaration) { - return (accessorDeclaration.Body == null) ? destinationMember : Cast(accessorDeclaration.AddBodyStatements(StatementGenerator.GenerateStatements(statements).ToArray())); + return (accessorDeclaration.Body == null) ? destinationMember : Cast(accessorDeclaration.AddBodyStatements([.. StatementGenerator.GenerateStatements(statements)])); } else if (destinationMember is CompilationUnitSyntax compilationUnit && info.Context.BestLocation is null) { @@ -509,7 +511,7 @@ public override TDeclarationNode AddStatements( // Insert the new global statement(s) at the end of any current global statements. // This code relies on 'LastIndexOf' returning -1 when no matching element is found. var insertionIndex = compilationUnit.Members.LastIndexOf(memberDeclaration => memberDeclaration.IsKind(SyntaxKind.GlobalStatement)) + 1; - var wrappedStatements = StatementGenerator.GenerateStatements(statements).Select(SyntaxFactory.GlobalStatement).ToArray(); + var wrappedStatements = StatementGenerator.GenerateStatements(statements).Select(GlobalStatement).ToArray(); return Cast(compilationUnit.WithMembers(compilationUnit.Members.InsertRange(insertionIndex, wrappedStatements))); } else if (destinationMember is StatementSyntax statement && statement.IsParentKind(SyntaxKind.GlobalStatement)) @@ -517,8 +519,8 @@ public override TDeclarationNode AddStatements( // We are adding a statement to a global statement in script, where the CompilationUnitSyntax is not a // statement container. If the global statement is not already a block, create a block which can hold // both the original statement and any new statements we are adding to it. - var block = statement as BlockSyntax ?? SyntaxFactory.Block(statement); - return Cast(block.AddStatements(StatementGenerator.GenerateStatements(statements).ToArray())); + var block = statement as BlockSyntax ?? Block(statement); + return Cast(block.AddStatements([.. StatementGenerator.GenerateStatements(statements)])); } else { @@ -613,10 +615,10 @@ private static TDeclarationNode AddStatementsToAnonymousFunctions(TDe public override TDeclarationNode UpdateDeclarationModifiers(TDeclarationNode declaration, IEnumerable newModifiers, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken) { - SyntaxTokenList computeNewModifiersList(SyntaxTokenList modifiersList) => newModifiers.ToSyntaxTokenList(); - return UpdateDeclarationModifiers(declaration, computeNewModifiersList); + return UpdateDeclarationModifiers(declaration, _ => [.. newModifiers]); } public override TDeclarationNode UpdateDeclarationAccessibility(TDeclarationNode declaration, Accessibility newAccessibility, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken) { - SyntaxTokenList computeNewModifiersList(SyntaxTokenList modifiersList) => UpdateDeclarationAccessibility(modifiersList, newAccessibility, info); - return UpdateDeclarationModifiers(declaration, computeNewModifiersList); + return UpdateDeclarationModifiers(declaration, modifiersList => UpdateDeclarationAccessibility(modifiersList, newAccessibility, info)); } private static SyntaxTokenList UpdateDeclarationAccessibility(SyntaxTokenList modifiersList, Accessibility newAccessibility, CSharpCodeGenerationContextInfo info) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConstructorGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConstructorGenerator.cs index 28646257a4deb..2b9e2e42986d6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConstructorGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConstructorGenerator.cs @@ -7,16 +7,17 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CodeGeneration; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; -using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; -using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers; namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CodeGenerationHelpers; +using static CSharpCodeGenerationHelpers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class ConstructorGenerator { private static MemberDeclarationSyntax? LastConstructorOrField(SyntaxList members) @@ -52,14 +53,14 @@ internal static ConstructorDeclarationSyntax GenerateConstructorDeclaration( var hasNoBody = !info.Context.GenerateMethodBodies; - var declaration = SyntaxFactory.ConstructorDeclaration( + var declaration = ConstructorDeclaration( attributeLists: AttributeGenerator.GenerateAttributeLists(constructor.GetAttributes(), info), modifiers: GenerateModifiers(constructor, info), identifier: CodeGenerationConstructorInfo.GetTypeName(constructor).ToIdentifierToken(), parameterList: ParameterGenerator.GenerateParameterList(constructor.Parameters, isExplicit: false, info: info), initializer: GenerateConstructorInitializer(constructor), body: hasNoBody ? null : GenerateBlock(constructor), - semicolonToken: hasNoBody ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : default); + semicolonToken: hasNoBody ? SemicolonToken : default); declaration = UseExpressionBodyIfDesired(info, declaration, cancellationToken); @@ -97,11 +98,11 @@ private static ConstructorDeclarationSyntax UseExpressionBodyIfDesired( return arguments == null ? null - : SyntaxFactory.ConstructorInitializer(kind).WithArgumentList(GenerateArgumentList(arguments)); + : ConstructorInitializer(kind).WithArgumentList(GenerateArgumentList(arguments)); } private static ArgumentListSyntax GenerateArgumentList(ImmutableArray arguments) - => SyntaxFactory.ArgumentList([.. arguments.Select(ArgumentGenerator.GenerateArgument)]); + => ArgumentList([.. arguments.Select(ArgumentGenerator.GenerateArgument)]); private static BlockSyntax GenerateBlock( IMethodSymbol constructor) @@ -110,27 +111,25 @@ private static BlockSyntax GenerateBlock( ? default : StatementGenerator.GenerateStatements(CodeGenerationConstructorInfo.GetStatements(constructor)); - return SyntaxFactory.Block(statements); + return Block(statements); } private static SyntaxTokenList GenerateModifiers(IMethodSymbol constructor, CSharpCodeGenerationContextInfo info) { - var tokens = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var tokens); if (constructor.IsStatic) { - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); } else { - CSharpCodeGenerationHelpers.AddAccessibilityModifiers(constructor.DeclaredAccessibility, tokens, info, Accessibility.Private); + AddAccessibilityModifiers(constructor.DeclaredAccessibility, tokens, info, Accessibility.Private); } if (CodeGenerationConstructorInfo.GetIsUnsafe(constructor)) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); - } + tokens.Add(UnsafeKeyword); - return tokens.ToSyntaxTokenListAndFree(); + return [.. tokens]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConversionGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConversionGenerator.cs index 388277d92dcef..62452baed9146 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConversionGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConversionGenerator.cs @@ -13,6 +13,9 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class ConversionGenerator { internal static TypeDeclarationSyntax AddConversionTo( @@ -54,25 +57,25 @@ private static ConversionOperatorDeclarationSyntax GenerateConversionDeclaration } var keyword = method.MetadataName == WellKnownMemberNames.ImplicitConversionName - ? SyntaxFactory.Token(SyntaxKind.ImplicitKeyword) - : SyntaxFactory.Token(SyntaxKind.ExplicitKeyword); + ? ImplicitKeyword + : ExplicitKeyword; var checkedToken = SyntaxFacts.IsCheckedOperator(method.MetadataName) - ? SyntaxFactory.Token(SyntaxKind.CheckedKeyword) + ? CheckedKeyword : default; - var declaration = SyntaxFactory.ConversionOperatorDeclaration( + var declaration = ConversionOperatorDeclaration( attributeLists: AttributeGenerator.GenerateAttributeLists(method.GetAttributes(), info), modifiers: GenerateModifiers(destination), implicitOrExplicitKeyword: keyword, explicitInterfaceSpecifier: null, - operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), + operatorKeyword: OperatorKeyword, checkedKeyword: checkedToken, type: method.ReturnType.GenerateTypeSyntax(), parameterList: ParameterGenerator.GenerateParameterList(method.Parameters, isExplicit: false, info: info), body: hasNoBody ? null : StatementGenerator.GenerateBlock(method), expressionBody: null, - semicolonToken: hasNoBody ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : new SyntaxToken()); + semicolonToken: hasNoBody ? SemicolonToken : new SyntaxToken()); declaration = UseExpressionBodyIfDesired(info, declaration, cancellationToken); @@ -100,15 +103,8 @@ private static ConversionOperatorDeclarationSyntax UseExpressionBodyIfDesired( private static SyntaxTokenList GenerateModifiers(CodeGenerationDestination destination) { // If these appear in interfaces they must be static abstract - if (destination is CodeGenerationDestination.InterfaceType) - { - return [ - SyntaxFactory.Token(SyntaxKind.StaticKeyword), - SyntaxFactory.Token(SyntaxKind.AbstractKeyword)]; - } - - return [ - SyntaxFactory.Token(SyntaxKind.PublicKeyword), - SyntaxFactory.Token(SyntaxKind.StaticKeyword)]; + return destination is CodeGenerationDestination.InterfaceType + ? ([StaticKeyword, AbstractKeyword]) + : ([PublicKeyword, StaticKeyword]); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/DestructorGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/DestructorGenerator.cs index 159f03ea4248d..491d4ffd85e5e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/DestructorGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/DestructorGenerator.cs @@ -13,6 +13,9 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class DestructorGenerator { private static MemberDeclarationSyntax? LastConstructorOrField(SyntaxList members) @@ -46,14 +49,14 @@ internal static DestructorDeclarationSyntax GenerateDestructorDeclaration( var hasNoBody = !info.Context.GenerateMethodBodies; - var declaration = SyntaxFactory.DestructorDeclaration( + var declaration = DestructorDeclaration( attributeLists: AttributeGenerator.GenerateAttributeLists(destructor.GetAttributes(), info), modifiers: default, - tildeToken: SyntaxFactory.Token(SyntaxKind.TildeToken), + tildeToken: TildeToken, identifier: CodeGenerationDestructorInfo.GetTypeName(destructor).ToIdentifierToken(), - parameterList: SyntaxFactory.ParameterList(), + parameterList: ParameterList(), body: hasNoBody ? null : GenerateBlock(destructor), - semicolonToken: hasNoBody ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : default); + semicolonToken: hasNoBody ? SemicolonToken : default); return AddFormatterAndCodeGeneratorAnnotationsTo( ConditionallyAddDocumentationCommentTo(declaration, destructor, info, cancellationToken)); @@ -66,6 +69,6 @@ private static BlockSyntax GenerateBlock( ? default : StatementGenerator.GenerateStatements(CodeGenerationDestructorInfo.GetStatements(constructor)); - return SyntaxFactory.Block(statements); + return Block(statements); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EnumMemberGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EnumMemberGenerator.cs index aa9928a932d1a..8e44b135cd38b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EnumMemberGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EnumMemberGenerator.cs @@ -12,11 +12,13 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Shared.Utilities; -using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; -using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers; - namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CodeGenerationHelpers; +using static CSharpCodeGenerationHelpers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class EnumMemberGenerator { internal static EnumDeclarationSyntax AddEnumMemberTo(EnumDeclarationSyntax destination, IFieldSymbol enumMember, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken) @@ -33,19 +35,19 @@ internal static EnumDeclarationSyntax AddEnumMemberTo(EnumDeclarationSyntax dest else if (members.LastOrDefault().Kind() == SyntaxKind.CommaToken) { members.Add(member); - members.Add(SyntaxFactory.Token(SyntaxKind.CommaToken)); + members.Add(CommaToken); } else { var lastMember = members.Last(); var trailingTrivia = lastMember.GetTrailingTrivia(); - members[members.Count - 1] = lastMember.WithTrailingTrivia(); - members.Add(SyntaxFactory.Token(SyntaxKind.CommaToken).WithTrailingTrivia(trailingTrivia)); + members[^1] = lastMember.WithTrailingTrivia(); + members.Add(CommaToken.WithTrailingTrivia(trailingTrivia)); members.Add(member); } return destination.EnsureOpenAndCloseBraceTokens() - .WithMembers(SyntaxFactory.SeparatedList(members)); + .WithMembers(SeparatedList(members)); } public static EnumMemberDeclarationSyntax GenerateEnumMemberDeclaration( @@ -61,8 +63,8 @@ public static EnumMemberDeclarationSyntax GenerateEnumMemberDeclaration( } var value = CreateEnumMemberValue(info.Generator, destination, enumMember); - var member = SyntaxFactory.EnumMemberDeclaration(enumMember.Name.ToIdentifierToken()) - .WithEqualsValue(value == null ? null : SyntaxFactory.EqualsValueClause(value: value)); + var member = EnumMemberDeclaration(enumMember.Name.ToIdentifierToken()) + .WithEqualsValue(value == null ? null : EqualsValueClause(value: value)); return AddFormatterAndCodeGeneratorAnnotationsTo( ConditionallyAddDocumentationCommentTo(member, enumMember, info, cancellationToken)); @@ -127,10 +129,10 @@ not long and var shiftValue = IntegerUtilities.LogBase2(value); // Re-use the numericLiteral text so type suffixes match too - return SyntaxFactory.BinaryExpression( + return BinaryExpression( SyntaxKind.LeftShiftExpression, - SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(numericLiteral.Token.Text, 1)), - SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(shiftValue.ToString(), shiftValue))); + LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(numericLiteral.Token.Text, 1)), + LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(shiftValue.ToString(), shiftValue))); } } } @@ -142,13 +144,13 @@ not long and if (numericText.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { // Hex - return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, - SyntaxFactory.Literal(numericText[..2] + value.ToString("X"), value)); + return LiteralExpression(SyntaxKind.NumericLiteralExpression, + Literal(numericText[..2] + value.ToString("X"), value)); } else if (numericText.StartsWith("0b", StringComparison.OrdinalIgnoreCase)) { - return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, - SyntaxFactory.Literal(numericText[..2] + Convert.ToString(value, 2), value)); + return LiteralExpression(SyntaxKind.NumericLiteralExpression, + Literal(numericText[..2] + Convert.ToString(value, 2), value)); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs index 1e5e90bd3c974..6891dcb45c718 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs @@ -12,11 +12,13 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; -using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers; - namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CodeGenerationHelpers; +using static CSharpCodeGenerationHelpers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class EventGenerator { private static MemberDeclarationSyntax? AfterMember( @@ -113,12 +115,12 @@ private static MemberDeclarationSyntax GenerateEventFieldDeclaration( { return AddFormatterAndCodeGeneratorAnnotationsTo( AddAnnotationsTo(@event, - SyntaxFactory.EventFieldDeclaration( + EventFieldDeclaration( AttributeGenerator.GenerateAttributeLists(@event.GetAttributes(), info), GenerateModifiers(@event, destination, info), - SyntaxFactory.VariableDeclaration( + VariableDeclaration( @event.Type.GenerateTypeSyntax(), - [SyntaxFactory.VariableDeclarator(@event.Name.ToIdentifierToken())])))); + [VariableDeclarator(@event.Name.ToIdentifierToken())])))); } private static MemberDeclarationSyntax GenerateEventDeclarationWorker( @@ -126,7 +128,7 @@ private static MemberDeclarationSyntax GenerateEventDeclarationWorker( { var explicitInterfaceSpecifier = GenerateExplicitInterfaceSpecifier(@event.ExplicitInterfaceImplementations); - return AddFormatterAndCodeGeneratorAnnotationsTo(SyntaxFactory.EventDeclaration( + return AddFormatterAndCodeGeneratorAnnotationsTo(EventDeclaration( attributeLists: AttributeGenerator.GenerateAttributeLists(@event.GetAttributes(), info), modifiers: GenerateModifiers(@event, destination, info), type: @event.Type.GenerateTypeSyntax(), @@ -144,7 +146,7 @@ private static AccessorListSyntax GenerateAccessorList( GenerateAccessorDeclaration(@event, @event.RemoveMethod, SyntaxKind.RemoveAccessorDeclaration, destination, info), }; - return SyntaxFactory.AccessorList([.. accessors.WhereNotNull()]); + return AccessorList([.. accessors.WhereNotNull()]); } private static AccessorDeclarationSyntax? GenerateAccessorDeclaration( @@ -165,14 +167,14 @@ private static AccessorDeclarationSyntax GenerateAccessorDeclaration( SyntaxKind kind, bool hasBody) { - return AddAnnotationsTo(accessor, SyntaxFactory.AccessorDeclaration(kind) + return AddAnnotationsTo(accessor, AccessorDeclaration(kind) .WithBody(hasBody ? GenerateBlock(accessor) : null) - .WithSemicolonToken(hasBody ? default : SyntaxFactory.Token(SyntaxKind.SemicolonToken))); + .WithSemicolonToken(hasBody ? default : SemicolonToken)); } private static BlockSyntax GenerateBlock(IMethodSymbol accessor) { - return SyntaxFactory.Block( + return Block( StatementGenerator.GenerateStatements(CodeGenerationMethodInfo.GetStatements(accessor))); } @@ -190,13 +192,13 @@ private static bool HasAccessorBodies( private static SyntaxTokenList GenerateModifiers( IEventSymbol @event, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info) { - var tokens = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var tokens); // Only "static" allowed if we're an explicit impl. if (@event.ExplicitInterfaceImplementations.Any()) { if (@event.IsStatic) - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); } else { @@ -205,19 +207,19 @@ private static SyntaxTokenList GenerateModifiers( { if (@event.IsStatic) { - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); // We only generate the abstract keyword in interfaces for static abstract members if (@event.IsAbstract) - tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); + tokens.Add(AbstractKeyword); } } else { - CSharpCodeGenerationHelpers.AddAccessibilityModifiers(@event.DeclaredAccessibility, tokens, info, Accessibility.Private); + AddAccessibilityModifiers(@event.DeclaredAccessibility, tokens, info, Accessibility.Private); if (@event.IsStatic) - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); // An event is readonly if its accessors are readonly. // If one accessor is readonly and the other one is not, @@ -225,19 +227,19 @@ private static SyntaxTokenList GenerateModifiers( // See https://github.com/dotnet/roslyn/issues/34213 // Don't show the readonly modifier if the containing type is already readonly if (@event.AddMethod?.IsReadOnly == true && !@event.ContainingType.IsReadOnly) - tokens.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + tokens.Add(ReadOnlyKeyword); if (@event.IsAbstract) - tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); + tokens.Add(AbstractKeyword); if (@event.IsOverride) - tokens.Add(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); + tokens.Add(OverrideKeyword); } } if (CodeGenerationEventInfo.GetIsUnsafe(@event)) - tokens.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + tokens.Add(UnsafeKeyword); - return tokens.ToSyntaxTokenListAndFree(); + return [.. tokens]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ExpressionGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ExpressionGenerator.cs index 1105c4fbf6067..1174ff620b305 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ExpressionGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ExpressionGenerator.cs @@ -8,17 +8,18 @@ using System.Linq; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Roslyn.Utilities; -using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; - namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CodeGenerationHelpers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class ExpressionGenerator { public static ExpressionSyntax GenerateExpression( @@ -32,14 +33,14 @@ public static ExpressionSyntax GenerateExpression( case TypedConstantKind.Type: return typedConstant.Value is ITypeSymbol typeSymbol - ? SyntaxFactory.TypeOfExpression(typeSymbol.GenerateTypeSyntax()) + ? TypeOfExpression(typeSymbol.GenerateTypeSyntax()) : GenerateNullLiteral(); case TypedConstantKind.Array: return typedConstant.IsNull ? GenerateNullLiteral() - : SyntaxFactory.ImplicitArrayCreationExpression( - SyntaxFactory.InitializerExpression(SyntaxKind.ArrayInitializerExpression, + : ImplicitArrayCreationExpression( + InitializerExpression(SyntaxKind.ArrayInitializerExpression, [.. typedConstant.Values.Select(v => GenerateExpression(generator, v))])); default: @@ -48,7 +49,7 @@ public static ExpressionSyntax GenerateExpression( } private static ExpressionSyntax GenerateNullLiteral() - => SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression); + => LiteralExpression(SyntaxKind.NullLiteralExpression); internal static ExpressionSyntax GenerateExpression( SyntaxGenerator generator, @@ -79,17 +80,17 @@ internal static ExpressionSyntax GenerateNonEnumValueExpression(SyntaxGenerator bool val => GenerateBooleanLiteralExpression(val), string val => GenerateStringLiteralExpression(val), char val => GenerateCharLiteralExpression(val), - sbyte val => GenerateLiteralExpression(type, val, LiteralSpecialValues.SByteSpecialValues, formatString: null, canUseFieldReference, (s, v) => SyntaxFactory.Literal(s, v), x => x < 0, x => (sbyte)-x, "128"), - short val => GenerateLiteralExpression(type, val, LiteralSpecialValues.Int16SpecialValues, formatString: null, canUseFieldReference, (s, v) => SyntaxFactory.Literal(s, v), x => x < 0, x => (short)-x, "32768"), - int val => GenerateLiteralExpression(type, val, LiteralSpecialValues.Int32SpecialValues, formatString: null, canUseFieldReference, SyntaxFactory.Literal, x => x < 0, x => -x, "2147483648"), - long val => GenerateLiteralExpression(type, val, LiteralSpecialValues.Int64SpecialValues, formatString: null, canUseFieldReference, SyntaxFactory.Literal, x => x < 0, x => -x, "9223372036854775808"), - byte val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.ByteSpecialValues, formatString: null, canUseFieldReference, (s, v) => SyntaxFactory.Literal(s, v)), - ushort val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.UInt16SpecialValues, formatString: null, canUseFieldReference, (s, v) => SyntaxFactory.Literal(s, (uint)v)), - uint val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.UInt32SpecialValues, formatString: null, canUseFieldReference, SyntaxFactory.Literal), - ulong val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.UInt64SpecialValues, formatString: null, canUseFieldReference, SyntaxFactory.Literal), + sbyte val => GenerateLiteralExpression(type, val, LiteralSpecialValues.SByteSpecialValues, formatString: null, canUseFieldReference, (s, v) => Literal(s, v), x => x < 0, x => (sbyte)-x, "128"), + short val => GenerateLiteralExpression(type, val, LiteralSpecialValues.Int16SpecialValues, formatString: null, canUseFieldReference, (s, v) => Literal(s, v), x => x < 0, x => (short)-x, "32768"), + int val => GenerateLiteralExpression(type, val, LiteralSpecialValues.Int32SpecialValues, formatString: null, canUseFieldReference, Literal, x => x < 0, x => -x, "2147483648"), + long val => GenerateLiteralExpression(type, val, LiteralSpecialValues.Int64SpecialValues, formatString: null, canUseFieldReference, Literal, x => x < 0, x => -x, "9223372036854775808"), + byte val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.ByteSpecialValues, formatString: null, canUseFieldReference, (s, v) => Literal(s, v)), + ushort val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.UInt16SpecialValues, formatString: null, canUseFieldReference, (s, v) => Literal(s, (uint)v)), + uint val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.UInt32SpecialValues, formatString: null, canUseFieldReference, Literal), + ulong val => GenerateNonNegativeLiteralExpression(type, val, LiteralSpecialValues.UInt64SpecialValues, formatString: null, canUseFieldReference, Literal), float val => GenerateSingleLiteralExpression(type, val, canUseFieldReference), double val => GenerateDoubleLiteralExpression(type, val, canUseFieldReference), - decimal val => GenerateLiteralExpression(type, val, LiteralSpecialValues.DecimalSpecialValues, formatString: null, canUseFieldReference, SyntaxFactory.Literal, x => x < 0, x => -x, integerMinValueString: null), + decimal val => GenerateLiteralExpression(type, val, LiteralSpecialValues.DecimalSpecialValues, formatString: null, canUseFieldReference, Literal, x => x < 0, x => -x, integerMinValueString: null), _ => type == null || type.IsReferenceType || type is IPointerTypeSymbol || type.IsNullable() ? GenerateNullLiteral() : (ExpressionSyntax)generator.DefaultExpression(type), @@ -97,7 +98,7 @@ internal static ExpressionSyntax GenerateNonEnumValueExpression(SyntaxGenerator private static ExpressionSyntax GenerateBooleanLiteralExpression(bool val) { - return SyntaxFactory.LiteralExpression(val + return LiteralExpression(val ? SyntaxKind.TrueLiteralExpression : SyntaxKind.FalseLiteralExpression); } @@ -105,15 +106,15 @@ private static ExpressionSyntax GenerateBooleanLiteralExpression(bool val) private static ExpressionSyntax GenerateStringLiteralExpression(string val) { var valueString = SymbolDisplay.FormatLiteral(val, quote: true); - return SyntaxFactory.LiteralExpression( - SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(valueString, val)); + return LiteralExpression( + SyntaxKind.StringLiteralExpression, Literal(valueString, val)); } private static ExpressionSyntax GenerateCharLiteralExpression(char val) { var literal = SymbolDisplay.FormatLiteral(val, quote: true); - return SyntaxFactory.LiteralExpression( - SyntaxKind.CharacterLiteralExpression, SyntaxFactory.Literal(literal, val)); + return LiteralExpression( + SyntaxKind.CharacterLiteralExpression, Literal(literal, val)); } private static string DetermineSuffix(ITypeSymbol? type, object value) @@ -176,27 +177,27 @@ private static ExpressionSyntax GenerateDoubleLiteralExpression(ITypeSymbol? typ { if (double.IsNaN(value)) { - return SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, + return BinaryExpression(SyntaxKind.DivideExpression, GenerateDoubleLiteralExpression(type: null, 0.0, canUseFieldReference: false), GenerateDoubleLiteralExpression(type: null, 0.0, canUseFieldReference: false)); } else if (double.IsPositiveInfinity(value)) { - return SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, + return BinaryExpression(SyntaxKind.DivideExpression, GenerateDoubleLiteralExpression(type: null, 1.0, canUseFieldReference: false), GenerateDoubleLiteralExpression(type: null, 0.0, canUseFieldReference: false)); } else if (double.IsNegativeInfinity(value)) { - return SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, - SyntaxFactory.PrefixUnaryExpression(SyntaxKind.UnaryMinusExpression, GenerateDoubleLiteralExpression(null, 1.0, false)), + return BinaryExpression(SyntaxKind.DivideExpression, + PrefixUnaryExpression(SyntaxKind.UnaryMinusExpression, GenerateDoubleLiteralExpression(null, 1.0, false)), GenerateDoubleLiteralExpression(type: null, 0.0, canUseFieldReference: false)); } } return GenerateLiteralExpression( type, value, LiteralSpecialValues.DoubleSpecialValues, formatString: "R", canUseFieldReference, - SyntaxFactory.Literal, x => x < 0, x => -x, integerMinValueString: null); + Literal, x => x < 0, x => -x, integerMinValueString: null); } private static ExpressionSyntax GenerateSingleLiteralExpression(ITypeSymbol? type, float value, bool canUseFieldReference) @@ -205,27 +206,27 @@ private static ExpressionSyntax GenerateSingleLiteralExpression(ITypeSymbol? typ { if (float.IsNaN(value)) { - return SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, + return BinaryExpression(SyntaxKind.DivideExpression, GenerateSingleLiteralExpression(type: null, 0.0F, canUseFieldReference: false), GenerateSingleLiteralExpression(type: null, 0.0F, canUseFieldReference: false)); } else if (float.IsPositiveInfinity(value)) { - return SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, + return BinaryExpression(SyntaxKind.DivideExpression, GenerateSingleLiteralExpression(type: null, 1.0F, canUseFieldReference: false), GenerateSingleLiteralExpression(type: null, 0.0F, canUseFieldReference: false)); } else if (float.IsNegativeInfinity(value)) { - return SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, - SyntaxFactory.PrefixUnaryExpression(SyntaxKind.UnaryMinusExpression, GenerateSingleLiteralExpression(null, 1.0F, false)), + return BinaryExpression(SyntaxKind.DivideExpression, + PrefixUnaryExpression(SyntaxKind.UnaryMinusExpression, GenerateSingleLiteralExpression(null, 1.0F, false)), GenerateSingleLiteralExpression(null, 0.0F, false)); } } return GenerateLiteralExpression( type, value, LiteralSpecialValues.SingleSpecialValues, formatString: "R", canUseFieldReference, - SyntaxFactory.Literal, x => x < 0, x => -x, null); + Literal, x => x < 0, x => -x, null); } private static ExpressionSyntax GenerateNonNegativeLiteralExpression( @@ -268,11 +269,11 @@ private static ExpressionSyntax GenerateLiteralExpression( ? (integerMinValueString ?? throw ExceptionUtilities.Unreachable()) : ((IFormattable)nonNegativeValue).ToString(formatString, CultureInfo.InvariantCulture) + suffix; - var literal = SyntaxFactory.LiteralExpression( + var literal = LiteralExpression( SyntaxKind.NumericLiteralExpression, tokenFactory(stringValue, nonNegativeValue)); return negative - ? SyntaxFactory.PrefixUnaryExpression(SyntaxKind.UnaryMinusExpression, literal) + ? PrefixUnaryExpression(SyntaxKind.UnaryMinusExpression, literal) : literal; } @@ -289,7 +290,7 @@ private static ExpressionSyntax GenerateLiteralExpression( memberAccess = memberAccess.WithAdditionalAnnotations(SpecialTypeAnnotation.Create(type.SpecialType)); } - var result = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, memberAccess, SyntaxFactory.IdentifierName(constant.Value)); + var result = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, memberAccess, IdentifierName(constant.Value)); return result.WithAdditionalAnnotations(Simplifier.Annotation); } } @@ -299,17 +300,17 @@ private static ExpressionSyntax GenerateLiteralExpression( private static ExpressionSyntax GenerateMemberAccess(params string[] names) { - ExpressionSyntax result = SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword)); + ExpressionSyntax result = IdentifierName(GlobalKeyword); for (var i = 0; i < names.Length; i++) { - var name = SyntaxFactory.IdentifierName(names[i]); + var name = IdentifierName(names[i]); if (i == 0) { - result = SyntaxFactory.AliasQualifiedName((IdentifierNameSyntax)result, name); + result = AliasQualifiedName((IdentifierNameSyntax)result, name); } else { - result = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, result, name); + result = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, result, name); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs index 1afaf4e705926..f6950b2e9d564 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs @@ -11,11 +11,14 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; -using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers; namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CodeGenerationHelpers; +using static CSharpCodeGenerationHelpers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class FieldGenerator { private static MemberDeclarationSyntax? LastField( @@ -91,15 +94,15 @@ public static FieldDeclarationSyntax GenerateFieldDeclaration( } var initializer = CodeGenerationFieldInfo.GetInitializer(field) is ExpressionSyntax initializerNode - ? SyntaxFactory.EqualsValueClause(initializerNode) + ? EqualsValueClause(initializerNode) : GenerateEqualsValue(info.Generator, field); - var fieldDeclaration = SyntaxFactory.FieldDeclaration( + var fieldDeclaration = FieldDeclaration( AttributeGenerator.GenerateAttributeLists(field.GetAttributes(), info), GenerateModifiers(field, info), - SyntaxFactory.VariableDeclaration( + VariableDeclaration( field.Type.GenerateTypeSyntax(), - [AddAnnotationsTo(field, SyntaxFactory.VariableDeclarator(field.Name.ToIdentifierToken(), null, initializer))])); + [AddAnnotationsTo(field, VariableDeclarator(field.Name.ToIdentifierToken(), null, initializer))])); return AddFormatterAndCodeGeneratorAnnotationsTo( ConditionallyAddDocumentationCommentTo(fieldDeclaration, field, info, cancellationToken)); @@ -110,7 +113,7 @@ public static FieldDeclarationSyntax GenerateFieldDeclaration( if (field.HasConstantValue) { var canUseFieldReference = field.Type != null && !field.Type.Equals(field.ContainingType); - return SyntaxFactory.EqualsValueClause(ExpressionGenerator.GenerateExpression(generator, field.Type, field.ConstantValue, canUseFieldReference)); + return EqualsValueClause(ExpressionGenerator.GenerateExpression(generator, field.Type, field.ConstantValue, canUseFieldReference)); } return null; @@ -118,36 +121,28 @@ public static FieldDeclarationSyntax GenerateFieldDeclaration( private static SyntaxTokenList GenerateModifiers(IFieldSymbol field, CSharpCodeGenerationContextInfo info) { - var tokens = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var tokens); - CSharpCodeGenerationHelpers.AddAccessibilityModifiers(field.DeclaredAccessibility, tokens, info, Accessibility.Private); + AddAccessibilityModifiers(field.DeclaredAccessibility, tokens, info, Accessibility.Private); if (field.IsConst) { - tokens.Add(SyntaxFactory.Token(SyntaxKind.ConstKeyword)); + tokens.Add(ConstKeyword); } else { if (field.IsStatic) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); - } + tokens.Add(StaticKeyword); if (field.IsReadOnly) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); - } + tokens.Add(ReadOnlyKeyword); if (field.IsRequired) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.RequiredKeyword)); - } + tokens.Add(RequiredKeyword); } if (CodeGenerationFieldInfo.GetIsUnsafe(field)) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); - } + tokens.Add(UnsafeKeyword); - return tokens.ToSyntaxTokenListAndFree(); + return [.. tokens]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs index 3e450fc522bcc..96beaea6d97c0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs @@ -8,21 +8,23 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CodeGeneration; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; -using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers; namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CodeGenerationHelpers; +using static CSharpCodeGenerationHelpers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class MethodGenerator { - private static readonly TypeParameterConstraintSyntax s_classConstraint = SyntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint); - private static readonly TypeParameterConstraintSyntax s_structConstraint = SyntaxFactory.ClassOrStructConstraint(SyntaxKind.StructConstraint); - private static readonly TypeParameterConstraintSyntax s_defaultConstraint = SyntaxFactory.DefaultConstraint(); + private static readonly TypeParameterConstraintSyntax s_classConstraint = ClassOrStructConstraint(SyntaxKind.ClassConstraint); + private static readonly TypeParameterConstraintSyntax s_structConstraint = ClassOrStructConstraint(SyntaxKind.StructConstraint); + private static readonly TypeParameterConstraintSyntax s_defaultConstraint = DefaultConstraint(); internal static BaseNamespaceDeclarationSyntax AddMethodTo( BaseNamespaceDeclarationSyntax destination, @@ -117,7 +119,7 @@ private static MethodDeclarationSyntax GenerateMethodDeclarationWorker( var explicitInterfaceSpecifier = GenerateExplicitInterfaceSpecifier(method.ExplicitInterfaceImplementations); - var methodDeclaration = SyntaxFactory.MethodDeclaration( + var methodDeclaration = MethodDeclaration( attributeLists: GenerateAttributes(method, info, explicitInterfaceSpecifier != null), modifiers: GenerateModifiers(method, destination, info), returnType: method.GenerateReturnTypeSyntax(), @@ -128,7 +130,7 @@ private static MethodDeclarationSyntax GenerateMethodDeclarationWorker( constraintClauses: GenerateConstraintClauses(method), body: hasNoBody ? null : StatementGenerator.GenerateBlock(method), expressionBody: null, - semicolonToken: hasNoBody ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : default); + semicolonToken: hasNoBody ? SemicolonToken : default); methodDeclaration = UseExpressionBodyIfDesired(info, methodDeclaration, cancellationToken); return AddFormatterAndCodeGeneratorAnnotationsTo(methodDeclaration); @@ -138,7 +140,7 @@ private static LocalFunctionStatementSyntax GenerateLocalFunctionDeclarationWork IMethodSymbol method, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken) { - var localFunctionDeclaration = SyntaxFactory.LocalFunctionStatement( + var localFunctionDeclaration = LocalFunctionStatement( modifiers: GenerateModifiers(method, destination, info), returnType: method.GenerateReturnTypeSyntax(), identifier: method.Name.ToIdentifierToken(), @@ -197,7 +199,7 @@ private static SyntaxList GenerateAttributes( if (!isExplicit) { attributes.AddRange(AttributeGenerator.GenerateAttributeLists(method.GetAttributes(), info)); - attributes.AddRange(AttributeGenerator.GenerateAttributeLists(method.GetReturnTypeAttributes(), info, SyntaxFactory.Token(SyntaxKind.ReturnKeyword))); + attributes.AddRange(AttributeGenerator.GenerateAttributeLists(method.GetReturnTypeAttributes(), info, ReturnKeyword)); } return [.. attributes]; @@ -235,7 +237,7 @@ private static SyntaxList GenerateDefaultCo _ => s_defaultConstraint }; - listOfClauses.Add(SyntaxFactory.TypeParameterConstraintClause( + listOfClauses.Add(TypeParameterConstraintClause( typeParameter.Name.ToIdentifierName(), [constraint])); } @@ -252,16 +254,16 @@ private static SyntaxList GenerateDefaultCo private static SyntaxTokenList GenerateModifiers( IMethodSymbol method, CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info) { - var tokens = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var tokens); // Only "static" and "unsafe" modifiers allowed if we're an explicit impl. if (method.ExplicitInterfaceImplementations.Any()) { if (method.IsStatic) - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); if (CodeGenerationMethodInfo.GetIsUnsafe(method)) - tokens.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + tokens.Add(UnsafeKeyword); } else { @@ -270,67 +272,63 @@ private static SyntaxTokenList GenerateModifiers( { if (method.IsStatic) { - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); // We only generate the abstract keyword in interfaces for static abstract members if (method.IsAbstract) - tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); + tokens.Add(AbstractKeyword); } } else if (destination is not CodeGenerationDestination.CompilationUnit and not CodeGenerationDestination.Namespace) { - CSharpCodeGenerationHelpers.AddAccessibilityModifiers(method.DeclaredAccessibility, tokens, info, Accessibility.Private); + AddAccessibilityModifiers(method.DeclaredAccessibility, tokens, info, Accessibility.Private); if (method.IsStatic) - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); if (method.IsAbstract) - tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); + tokens.Add(AbstractKeyword); if (method.IsSealed) - tokens.Add(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); + tokens.Add(SealedKeyword); // Don't show the readonly modifier if the containing type is already readonly // ContainingSymbol is used to guard against methods which are not members of their ContainingType (e.g. lambdas and local functions) if (method.IsReadOnly && (method.ContainingSymbol as INamedTypeSymbol)?.IsReadOnly != true) - tokens.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + tokens.Add(ReadOnlyKeyword); if (method.IsOverride) - tokens.Add(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); + tokens.Add(OverrideKeyword); if (method.IsVirtual) - tokens.Add(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); + tokens.Add(VirtualKeyword); if (CodeGenerationMethodInfo.GetIsPartial(method) && !method.IsAsync) - tokens.Add(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); + tokens.Add(PartialKeyword); } else if (destination is CodeGenerationDestination.CompilationUnit) { if (method.IsStatic) - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); } if (CodeGenerationMethodInfo.GetIsUnsafe(method)) - tokens.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + tokens.Add(UnsafeKeyword); if (CodeGenerationMethodInfo.GetIsNew(method)) - tokens.Add(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + tokens.Add(NewKeyword); } if (destination != CodeGenerationDestination.InterfaceType) { if (CodeGenerationMethodInfo.GetIsAsyncMethod(method)) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.AsyncKeyword)); - } + tokens.Add(AsyncKeyword); } if (CodeGenerationMethodInfo.GetIsPartial(method) && method.IsAsync) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); - } + tokens.Add(PartialKeyword); - return tokens.ToSyntaxTokenListAndFree(); + return [.. tokens]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs index 67d3c77b2afd2..810a767498d1b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs @@ -12,11 +12,14 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; -using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers; namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CodeGenerationHelpers; +using static CSharpCodeGenerationHelpers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class NamedTypeGenerator { public static TypeDeclarationSyntax AddNamedTypeTo( @@ -124,11 +127,11 @@ private static RecordDeclarationSyntax GenerateRecordMembers( // If there are no members, just make a simple record with no body if (members.Length == 0) - return recordDeclaration.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + return recordDeclaration.WithSemicolonToken(SemicolonToken); // Otherwise, give the record a body and add the members to it. - recordDeclaration = recordDeclaration.WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken)) - .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken)) + recordDeclaration = recordDeclaration.WithOpenBraceToken(OpenBraceToken) + .WithCloseBraceToken(CloseBraceToken) .WithSemicolonToken(default); return service.AddMembers(recordDeclaration, members, info, cancellationToken); } @@ -194,19 +197,19 @@ private static MemberDeclarationSyntax GetDeclarationSyntaxWithoutMembersWorker( { var isRecordClass = namedType.TypeKind is TypeKind.Class; var declarationKind = isRecordClass ? SyntaxKind.RecordDeclaration : SyntaxKind.RecordStructDeclaration; - var classOrStructKeyword = SyntaxFactory.Token(isRecordClass ? default : SyntaxKind.StructKeyword); + var classOrStructKeyword = Token(isRecordClass ? default : SyntaxKind.StructKeyword); - typeDeclaration = SyntaxFactory.RecordDeclaration(kind: declarationKind, attributeLists: default, modifiers: default, - SyntaxFactory.Token(SyntaxKind.RecordKeyword), classOrStructKeyword, namedType.Name.ToIdentifierToken(), + typeDeclaration = RecordDeclaration(kind: declarationKind, attributeLists: default, modifiers: default, + RecordKeyword, classOrStructKeyword, namedType.Name.ToIdentifierToken(), typeParameterList: null, parameterList: null, baseList: null, constraintClauses: default, openBraceToken: default, members: default, closeBraceToken: default, - SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + SemicolonToken); } else { var kind = namedType.TypeKind == TypeKind.Struct ? SyntaxKind.StructDeclaration : namedType.TypeKind == TypeKind.Interface ? SyntaxKind.InterfaceDeclaration : SyntaxKind.ClassDeclaration; - typeDeclaration = SyntaxFactory.TypeDeclaration(kind, namedType.Name.ToIdentifierToken()); + typeDeclaration = TypeDeclaration(kind, namedType.Name.ToIdentifierToken()); } var result = typeDeclaration @@ -227,7 +230,7 @@ private static DelegateDeclarationSyntax GenerateDelegateDeclaration( var invokeMethod = namedType.DelegateInvokeMethod; Contract.ThrowIfNull(invokeMethod); - return SyntaxFactory.DelegateDeclaration( + return DelegateDeclaration( GenerateAttributeDeclarations(namedType, info), GenerateModifiers(namedType, destination, info), invokeMethod.ReturnType.GenerateTypeSyntax(), @@ -243,10 +246,10 @@ private static EnumDeclarationSyntax GenerateEnumDeclaration( CSharpCodeGenerationContextInfo info) { var baseList = namedType.EnumUnderlyingType != null && namedType.EnumUnderlyingType.SpecialType != SpecialType.System_Int32 - ? SyntaxFactory.BaseList([SyntaxFactory.SimpleBaseType(namedType.EnumUnderlyingType.GenerateTypeSyntax())]) + ? BaseList([SimpleBaseType(namedType.EnumUnderlyingType.GenerateTypeSyntax())]) : null; - return SyntaxFactory.EnumDeclaration( + return EnumDeclaration( GenerateAttributeDeclarations(namedType, info), GenerateModifiers(namedType, destination, info), namedType.Name.ToIdentifierToken(), @@ -265,51 +268,43 @@ private static SyntaxTokenList GenerateModifiers( CodeGenerationDestination destination, CSharpCodeGenerationContextInfo info) { - var tokens = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var tokens); if (!namedType.IsFileLocal) { var defaultAccessibility = destination is CodeGenerationDestination.CompilationUnit or CodeGenerationDestination.Namespace ? Accessibility.Internal : Accessibility.Private; - CSharpCodeGenerationHelpers.AddAccessibilityModifiers(namedType.DeclaredAccessibility, tokens, info, defaultAccessibility); + AddAccessibilityModifiers(namedType.DeclaredAccessibility, tokens, info, defaultAccessibility); } else { - tokens.Add(SyntaxFactory.Token(SyntaxKind.FileKeyword)); + tokens.Add(FileKeyword); } if (namedType.IsStatic) { - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); } else { if (namedType.TypeKind == TypeKind.Class) { if (namedType.IsAbstract) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); - } + tokens.Add(AbstractKeyword); if (namedType.IsSealed) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); - } + tokens.Add(SealedKeyword); } } if (namedType.IsReadOnly) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); - } + tokens.Add(ReadOnlyKeyword); if (namedType.IsRefLikeType) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.RefKeyword)); - } + tokens.Add(RefKeyword); - return tokens.ToSyntaxTokenListAndFree(); + return [.. tokens]; } private static TypeParameterListSyntax? GenerateTypeParameterList( @@ -322,15 +317,15 @@ private static SyntaxTokenList GenerateModifiers( { var types = new List(); if (namedType.TypeKind == TypeKind.Class && namedType.BaseType != null && namedType.BaseType.SpecialType != Microsoft.CodeAnalysis.SpecialType.System_Object) - types.Add(SyntaxFactory.SimpleBaseType(namedType.BaseType.GenerateTypeSyntax())); + types.Add(SimpleBaseType(namedType.BaseType.GenerateTypeSyntax())); foreach (var type in namedType.Interfaces) - types.Add(SyntaxFactory.SimpleBaseType(type.GenerateTypeSyntax())); + types.Add(SimpleBaseType(type.GenerateTypeSyntax())); if (types.Count == 0) return null; - return SyntaxFactory.BaseList([.. types]); + return BaseList([.. types]); } private static SyntaxList GenerateConstraintClauses(INamedTypeSymbol namedType) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamespaceGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamespaceGenerator.cs index 7a958cda09908..293a5e8e041a5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamespaceGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamespaceGenerator.cs @@ -8,7 +8,6 @@ using System.Threading; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; @@ -18,6 +17,8 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static SyntaxFactory; + internal static class NamespaceGenerator { public static BaseNamespaceDeclarationSyntax AddNamespaceTo( @@ -102,16 +103,16 @@ private static SyntaxNode GenerateNamespaceDeclarationWorker( // If they're just generating the empty namespace then make that into compilation unit. if (name == string.Empty) - return SyntaxFactory.CompilationUnit().WithUsings(usings); + return CompilationUnit().WithUsings(usings); if (destination == CodeGenerationDestination.CompilationUnit && info.Options.NamespaceDeclarations.Value == NamespaceDeclarationPreference.FileScoped && info.LanguageVersion >= LanguageVersion.CSharp10) { - return SyntaxFactory.FileScopedNamespaceDeclaration(SyntaxFactory.ParseName(name)).WithUsings(usings); + return FileScopedNamespaceDeclaration(ParseName(name)).WithUsings(usings); } - return SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(name)).WithUsings(usings); + return NamespaceDeclaration(ParseName(name)).WithUsings(usings); } private static SyntaxNode GetDeclarationSyntaxWithoutMembers( @@ -153,8 +154,8 @@ private static SyntaxList GenerateUsingDirectives(INamespa var name = GenerateName(alias.Target); if (name != null) { - return SyntaxFactory.UsingDirective( - SyntaxFactory.NameEquals(alias.Name.ToIdentifierName()), + return UsingDirective( + NameEquals(alias.Name.ToIdentifierName()), name); } } @@ -163,7 +164,7 @@ private static SyntaxList GenerateUsingDirectives(INamespa var name = GenerateName(namespaceOrType); if (name != null) { - return SyntaxFactory.UsingDirective(name); + return UsingDirective(name); } } @@ -174,6 +175,6 @@ private static NameSyntax GenerateName(INamespaceOrTypeSymbol symbol) { return symbol is ITypeSymbol type ? type.GenerateNameSyntax() - : SyntaxFactory.ParseName(symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + : ParseName(symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/OperatorGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/OperatorGenerator.cs index 26c097c12b235..1e0c1fd58a445 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/OperatorGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/OperatorGenerator.cs @@ -5,17 +5,19 @@ using System; using System.Collections.Generic; using System.Threading; -using System.Xml; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; -using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; -using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers; namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CodeGenerationHelpers; +using static CSharpCodeGenerationHelpers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class OperatorGenerator { internal static TypeDeclarationSyntax AddOperatorTo( @@ -82,23 +84,23 @@ private static OperatorDeclarationSyntax GenerateOperatorDeclarationWorker( throw new ArgumentException(string.Format(WorkspaceExtensionsResources.Cannot_generate_code_for_unsupported_operator_0, method.Name), nameof(method)); } - var operatorToken = SyntaxFactory.Token(operatorSyntaxKind); + var operatorToken = Token(operatorSyntaxKind); var checkedToken = SyntaxFacts.IsCheckedOperator(method.MetadataName) - ? SyntaxFactory.Token(SyntaxKind.CheckedKeyword) + ? CheckedKeyword : default; - var operatorDecl = SyntaxFactory.OperatorDeclaration( + var operatorDecl = OperatorDeclaration( attributeLists: AttributeGenerator.GenerateAttributeLists(method.GetAttributes(), info), modifiers: GenerateModifiers(method, destination, hasNoBody), returnType: method.ReturnType.GenerateTypeSyntax(), explicitInterfaceSpecifier: GenerateExplicitInterfaceSpecifier(method.ExplicitInterfaceImplementations), - operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), + operatorKeyword: OperatorKeyword, checkedKeyword: checkedToken, operatorToken: operatorToken, parameterList: ParameterGenerator.GenerateParameterList(method.Parameters, isExplicit: false, info: info), body: hasNoBody ? null : StatementGenerator.GenerateBlock(method), expressionBody: null, - semicolonToken: hasNoBody ? SyntaxFactory.Token(SyntaxKind.SemicolonToken) : new SyntaxToken()); + semicolonToken: hasNoBody ? SemicolonToken : new SyntaxToken()); operatorDecl = UseExpressionBodyIfDesired(info, operatorDecl, cancellationToken); return operatorDecl; @@ -111,16 +113,14 @@ private static SyntaxTokenList GenerateModifiers(IMethodSymbol method, CodeGener if (method.ExplicitInterfaceImplementations.Length == 0 && !(destination is CodeGenerationDestination.InterfaceType && hasNoBody)) { - tokens.Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + tokens.Add(PublicKeyword); } - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); if (method.IsAbstract) - { - tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); - } + tokens.Add(AbstractKeyword); - return tokens.ToImmutableAndClear().ToSyntaxTokenList(); + return [.. tokens.ToImmutableAndClear()]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs index 3b7c79d31be7f..dde1cec02d278 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs @@ -3,17 +3,19 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers; namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CSharpCodeGenerationHelpers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class ParameterGenerator { public static ParameterListSyntax GenerateParameterList( @@ -23,7 +25,7 @@ public static ParameterListSyntax GenerateParameterList( { var parameters = GetParameters(parameterDefinitions, isExplicit, info); - return SyntaxFactory.ParameterList([.. parameters]); + return ParameterList([.. parameters]); } public static BracketedParameterListSyntax GenerateBracketedParameterList( @@ -35,7 +37,7 @@ public static BracketedParameterListSyntax GenerateBracketedParameterList( // could never have a typeParameterMapping. var parameters = GetParameters(parameterDefinitions, isExplicit, info); - return SyntaxFactory.BracketedParameterList([.. parameters]); + return BracketedParameterList([.. parameters]); } internal static ImmutableArray GetParameters( @@ -43,10 +45,10 @@ internal static ImmutableArray GetParameters( bool isExplicit, CSharpCodeGenerationContextInfo info) { - using var _ = ArrayBuilder.GetInstance(out var result); var seenOptional = false; var isFirstParam = true; + var result = new FixedSizeArrayBuilder(parameterDefinitions.Length); foreach (var p in parameterDefinitions) { var parameter = GetParameter(p, info, isExplicit, isFirstParam, seenOptional); @@ -55,7 +57,7 @@ internal static ImmutableArray GetParameters( isFirstParam = false; } - return result.ToImmutable(); + return result.MoveToImmutable(); } internal static ParameterSyntax GetParameter(IParameterSymbol parameter, CSharpCodeGenerationContextInfo info, bool isExplicit, bool isFirstParam, bool seenOptional) @@ -64,7 +66,7 @@ internal static ParameterSyntax GetParameter(IParameterSymbol parameter, CSharpC if (reusableSyntax != null) return reusableSyntax; - return SyntaxFactory.Parameter(parameter.Name.ToIdentifierToken()) + return Parameter(parameter.Name.ToIdentifierToken()) .WithAttributeLists(GenerateAttributes(parameter, isExplicit, info)) .WithModifiers(GenerateModifiers(parameter, isFirstParam)) .WithType(parameter.Type.GenerateTypeSyntax()) @@ -80,12 +82,12 @@ private static SyntaxTokenList GenerateModifiers( parameter.ContainingSymbol is IMethodSymbol methodSymbol && methodSymbol.IsExtensionMethod) { - list = list.Add(SyntaxFactory.Token(SyntaxKind.ThisKeyword)); + list = list.Add(ThisKeyword); } if (parameter.IsParams) { - list = list.Add(SyntaxFactory.Token(SyntaxKind.ParamsKeyword)); + list = list.Add(ParamsKeyword); } return list; @@ -105,7 +107,7 @@ parameter.ContainingSymbol is IMethodSymbol methodSymbol && if (defaultValue is DateTime) return null; - return SyntaxFactory.EqualsValueClause( + return EqualsValueClause( GenerateEqualsValueClauseWorker(generator, parameter, defaultValue)); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs index 0bc31a2c452be..fb5130ab4ee32 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs @@ -14,11 +14,13 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers; -using static Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationHelpers; - namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CodeGenerationHelpers; +using static CSharpCodeGenerationHelpers; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class PropertyGenerator { public static bool CanBeGenerated(IPropertySymbol property) @@ -92,7 +94,7 @@ private static MemberDeclarationSyntax GenerateIndexerDeclaration( { var explicitInterfaceSpecifier = GenerateExplicitInterfaceSpecifier(property.ExplicitInterfaceImplementations); - var declaration = SyntaxFactory.IndexerDeclaration( + var declaration = IndexerDeclaration( attributeLists: AttributeGenerator.GenerateAttributeLists(property.GetAttributes(), info), modifiers: GenerateModifiers(property, destination, info), type: GenerateTypeSyntax(property), @@ -110,14 +112,14 @@ private static MemberDeclarationSyntax GeneratePropertyDeclaration( CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken) { var initializer = CodeGenerationPropertyInfo.GetInitializer(property) is ExpressionSyntax initializerNode - ? SyntaxFactory.EqualsValueClause(initializerNode) + ? EqualsValueClause(initializerNode) : null; var explicitInterfaceSpecifier = GenerateExplicitInterfaceSpecifier(property.ExplicitInterfaceImplementations); var accessorList = GenerateAccessorList(property, destination, info, cancellationToken); - var propertyDeclaration = SyntaxFactory.PropertyDeclaration( + var propertyDeclaration = PropertyDeclaration( attributeLists: AttributeGenerator.GenerateAttributeLists(property.GetAttributes(), info), modifiers: GenerateModifiers(property, destination, info), type: GenerateTypeSyntax(property), @@ -126,7 +128,7 @@ private static MemberDeclarationSyntax GeneratePropertyDeclaration( accessorList: accessorList, expressionBody: null, initializer: initializer, - semicolonToken: initializer is null ? default : SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + semicolonToken: initializer is null ? default : SemicolonToken); propertyDeclaration = UseExpressionBodyIfDesired(info, propertyDeclaration, cancellationToken); @@ -268,7 +270,7 @@ private static bool TryGetArrowExpressionBody( return accessors[0] == null && accessors[1] == null ? null - : SyntaxFactory.AccessorList([.. accessors.WhereNotNull()]); + : AccessorList([.. accessors.WhereNotNull()]); } private static AccessorDeclarationSyntax? GenerateAccessorDeclaration( @@ -293,10 +295,10 @@ private static AccessorDeclarationSyntax GenerateAccessorDeclaration( CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken) { - var declaration = SyntaxFactory.AccessorDeclaration(kind) + var declaration = AccessorDeclaration(kind) .WithModifiers(GenerateAccessorModifiers(property, accessor, info)) .WithBody(hasBody ? GenerateBlock(accessor) : null) - .WithSemicolonToken(hasBody ? default : SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + .WithSemicolonToken(hasBody ? default : SemicolonToken); declaration = UseExpressionBodyIfDesired(info, declaration, cancellationToken); @@ -305,7 +307,7 @@ private static AccessorDeclarationSyntax GenerateAccessorDeclaration( private static BlockSyntax GenerateBlock(IMethodSymbol accessor) { - return SyntaxFactory.Block( + return Block( StatementGenerator.GenerateStatements(CodeGenerationMethodInfo.GetStatements(accessor))); } @@ -325,21 +327,19 @@ private static SyntaxTokenList GenerateAccessorModifiers( IMethodSymbol accessor, CSharpCodeGenerationContextInfo info) { - var modifiers = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var modifiers); if (accessor.DeclaredAccessibility != Accessibility.NotApplicable && accessor.DeclaredAccessibility != property.DeclaredAccessibility) { - CSharpCodeGenerationHelpers.AddAccessibilityModifiers(accessor.DeclaredAccessibility, modifiers, info, property.DeclaredAccessibility); + AddAccessibilityModifiers(accessor.DeclaredAccessibility, modifiers, info, property.DeclaredAccessibility); } var hasNonReadOnlyAccessor = property.GetMethod?.IsReadOnly == false || property.SetMethod?.IsReadOnly == false; if (hasNonReadOnlyAccessor && accessor.IsReadOnly) - { - modifiers.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); - } + modifiers.Add(ReadOnlyKeyword); - return modifiers.ToSyntaxTokenListAndFree(); + return [.. modifiers]; } private static SyntaxTokenList GenerateModifiers( @@ -351,7 +351,7 @@ private static SyntaxTokenList GenerateModifiers( if (property.ExplicitInterfaceImplementations.Any()) { if (property.IsStatic) - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); } else { @@ -360,18 +360,18 @@ private static SyntaxTokenList GenerateModifiers( { if (property.IsStatic) { - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); if (property.IsAbstract) - tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); + tokens.Add(AbstractKeyword); } } else if (destination is not CodeGenerationDestination.CompilationUnit) { - CSharpCodeGenerationHelpers.AddAccessibilityModifiers(property.DeclaredAccessibility, tokens, info, Accessibility.Private); + AddAccessibilityModifiers(property.DeclaredAccessibility, tokens, info, Accessibility.Private); if (property.IsStatic) - tokens.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + tokens.Add(StaticKeyword); // note: explicit interface impls are allowed to be 'readonly' but it never actually affects callers // because of the boxing requirement in order to call the method. @@ -380,28 +380,28 @@ private static SyntaxTokenList GenerateModifiers( // Don't show the readonly modifier if the containing type is already readonly if (hasAllReadOnlyAccessors && !property.ContainingType.IsReadOnly) - tokens.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + tokens.Add(ReadOnlyKeyword); if (property.IsSealed) - tokens.Add(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); + tokens.Add(SealedKeyword); if (property.IsOverride) - tokens.Add(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); + tokens.Add(OverrideKeyword); if (property.IsVirtual) - tokens.Add(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); + tokens.Add(VirtualKeyword); if (property.IsAbstract) - tokens.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); + tokens.Add(AbstractKeyword); if (property.IsRequired) - tokens.Add(SyntaxFactory.Token(SyntaxKind.RequiredKeyword)); + tokens.Add(RequiredKeyword); } } if (CodeGenerationPropertyInfo.GetIsUnsafe(property)) - tokens.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + tokens.Add(UnsafeKeyword); - return tokens.ToSyntaxTokenList(); + return [.. tokens]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/TypeParameterGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/TypeParameterGenerator.cs index a8c1df4a28190..2e4953249807e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/TypeParameterGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/TypeParameterGenerator.cs @@ -5,12 +5,14 @@ using System.Collections.Immutable; using System.Linq; -using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static class TypeParameterGenerator { public static TypeParameterListSyntax? GenerateTypeParameterList( @@ -18,17 +20,17 @@ internal static class TypeParameterGenerator { return typeParameters.Length == 0 ? null - : SyntaxFactory.TypeParameterList( + : TypeParameterList( [.. typeParameters.Select(t => GenerateTypeParameter(t, info))]); } private static TypeParameterSyntax GenerateTypeParameter(ITypeParameterSymbol symbol, CSharpCodeGenerationContextInfo info) { var varianceKeyword = - symbol.Variance == VarianceKind.In ? SyntaxFactory.Token(SyntaxKind.InKeyword) : - symbol.Variance == VarianceKind.Out ? SyntaxFactory.Token(SyntaxKind.OutKeyword) : default; + symbol.Variance == VarianceKind.In ? InKeyword : + symbol.Variance == VarianceKind.Out ? OutKeyword : default; - return SyntaxFactory.TypeParameter( + return TypeParameter( AttributeGenerator.GenerateAttributeLists(symbol.GetAttributes(), info), varianceKeyword, symbol.Name.ToIdentifierToken()); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ExpressionSyntaxExtensions.cs index 75dcffa498b64..d8edb44cd813f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ExpressionSyntaxExtensions.cs @@ -11,6 +11,8 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions; +using static SyntaxFactory; + internal static partial class ExpressionSyntaxExtensions { public static ExpressionSyntax Parenthesize( @@ -55,11 +57,11 @@ private static ExpressionSyntax ParenthesizeWorker( { var withoutTrivia = expression.WithoutTrivia(); var parenthesized = includeElasticTrivia - ? SyntaxFactory.ParenthesizedExpression(withoutTrivia) - : SyntaxFactory.ParenthesizedExpression( - SyntaxFactory.Token(SyntaxTriviaList.Empty, SyntaxKind.OpenParenToken, SyntaxTriviaList.Empty), + ? ParenthesizedExpression(withoutTrivia) + : ParenthesizedExpression( + Token(SyntaxTriviaList.Empty, SyntaxKind.OpenParenToken, SyntaxTriviaList.Empty), withoutTrivia, - SyntaxFactory.Token(SyntaxTriviaList.Empty, SyntaxKind.CloseParenToken, SyntaxTriviaList.Empty)); + Token(SyntaxTriviaList.Empty, SyntaxKind.CloseParenToken, SyntaxTriviaList.Empty)); return parenthesized.WithTriviaFrom(expression); } @@ -69,11 +71,11 @@ public static PatternSyntax Parenthesize( { var withoutTrivia = pattern.WithoutTrivia(); var parenthesized = includeElasticTrivia - ? SyntaxFactory.ParenthesizedPattern(withoutTrivia) - : SyntaxFactory.ParenthesizedPattern( - SyntaxFactory.Token(SyntaxTriviaList.Empty, SyntaxKind.OpenParenToken, SyntaxTriviaList.Empty), + ? ParenthesizedPattern(withoutTrivia) + : ParenthesizedPattern( + Token(SyntaxTriviaList.Empty, SyntaxKind.OpenParenToken, SyntaxTriviaList.Empty), withoutTrivia, - SyntaxFactory.Token(SyntaxTriviaList.Empty, SyntaxKind.CloseParenToken, SyntaxTriviaList.Empty)); + Token(SyntaxTriviaList.Empty, SyntaxKind.CloseParenToken, SyntaxTriviaList.Empty)); var result = parenthesized.WithTriviaFrom(pattern); return addSimplifierAnnotation @@ -86,7 +88,7 @@ public static CastExpressionSyntax Cast( ITypeSymbol targetType) { var parenthesized = expression.Parenthesize(); - var castExpression = SyntaxFactory.CastExpression( + var castExpression = CastExpression( targetType.GenerateTypeSyntax(), parenthesized.WithoutTrivia()).WithTriviaFrom(parenthesized); return castExpression.WithAdditionalAnnotations(Simplifier.Annotation); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeParameterSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeParameterSymbolExtensions.cs index 37d8030e3dcda..27e8300214536 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeParameterSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeParameterSymbolExtensions.cs @@ -10,6 +10,8 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions; +using static SyntaxFactory; + internal static class ITypeParameterSymbolExtensions { public static SyntaxList GenerateConstraintClauses( @@ -39,19 +41,19 @@ private static void AddConstraintClauses( if (typeParameter.HasReferenceTypeConstraint) { - constraints.Add(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint)); + constraints.Add(ClassOrStructConstraint(SyntaxKind.ClassConstraint)); } else if (typeParameter.HasUnmanagedTypeConstraint) { - constraints.Add(SyntaxFactory.TypeConstraint(SyntaxFactory.IdentifierName("unmanaged"))); + constraints.Add(TypeConstraint(IdentifierName("unmanaged"))); } else if (typeParameter.HasValueTypeConstraint) { - constraints.Add(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.StructConstraint)); + constraints.Add(ClassOrStructConstraint(SyntaxKind.StructConstraint)); } else if (typeParameter.HasNotNullConstraint) { - constraints.Add(SyntaxFactory.TypeConstraint(SyntaxFactory.IdentifierName("notnull"))); + constraints.Add(TypeConstraint(IdentifierName("notnull"))); } var constraintTypes = @@ -63,13 +65,13 @@ private static void AddConstraintClauses( { if (type.SpecialType != SpecialType.System_Object) { - constraints.Add(SyntaxFactory.TypeConstraint(type.GenerateTypeSyntax())); + constraints.Add(TypeConstraint(type.GenerateTypeSyntax())); } } if (typeParameter.HasConstructorConstraint) { - constraints.Add(SyntaxFactory.ConstructorConstraint()); + constraints.Add(ConstructorConstraint()); } if (constraints.Count == 0) @@ -77,7 +79,7 @@ private static void AddConstraintClauses( return; } - clauses.Add(SyntaxFactory.TypeParameterConstraintClause( + clauses.Add(TypeParameterConstraintClause( typeParameter.Name.ToIdentifierName(), [.. constraints])); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.ExpressionSyntaxGeneratorVisitor.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.ExpressionSyntaxGeneratorVisitor.cs index 7421eed99f672..43e42f7ab24a8 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.ExpressionSyntaxGeneratorVisitor.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.ExpressionSyntaxGeneratorVisitor.cs @@ -8,6 +8,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal partial class ITypeSymbolExtensions { private class ExpressionSyntaxGeneratorVisitor : SymbolVisitor @@ -29,7 +32,7 @@ public override ExpressionSyntax DefaultVisit(ISymbol symbol) private static TExpressionSyntax AddInformationTo(TExpressionSyntax syntax, ISymbol symbol) where TExpressionSyntax : ExpressionSyntax { - syntax = syntax.WithPrependedLeadingTrivia(SyntaxFactory.ElasticMarker).WithAppendedTrailingTrivia(SyntaxFactory.ElasticMarker); + syntax = syntax.WithPrependedLeadingTrivia(ElasticMarker).WithAppendedTrailingTrivia(ElasticMarker); syntax = syntax.WithAdditionalAnnotations(SymbolAnnotation.Create(symbol)); return syntax; @@ -64,8 +67,8 @@ public override ExpressionSyntax VisitNamedType(INamedTypeSymbol symbol) if (symbol.TypeKind != TypeKind.Error) { return AddInformationTo( - SyntaxFactory.AliasQualifiedName( - SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword)), + AliasQualifiedName( + IdentifierName(GlobalKeyword), simpleNameSyntax), symbol); } } @@ -90,8 +93,8 @@ public override ExpressionSyntax VisitNamespace(INamespaceSymbol symbol) if (symbol.ContainingNamespace.IsGlobalNamespace) { return AddInformationTo( - SyntaxFactory.AliasQualifiedName( - SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword)), + AliasQualifiedName( + IdentifierName(GlobalKeyword), syntax), symbol); } else @@ -104,7 +107,7 @@ public override ExpressionSyntax VisitNamespace(INamespaceSymbol symbol) private static MemberAccessExpressionSyntax CreateMemberAccessExpression( ISymbol symbol, ExpressionSyntax container, SimpleNameSyntax syntax) { - return AddInformationTo(SyntaxFactory.MemberAccessExpression( + return AddInformationTo(MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, container, syntax), symbol); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs index 9dc82540b27a0..c7d6581c6c6a4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs @@ -3,13 +3,10 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Threading; using Microsoft.CodeAnalysis.CSharp.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; @@ -17,6 +14,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal partial class ITypeSymbolExtensions { private class TypeSyntaxGeneratorVisitor : SymbolVisitor @@ -38,7 +38,7 @@ public override TypeSyntax DefaultVisit(ISymbol node) private static TTypeSyntax AddInformationTo(TTypeSyntax syntax, ISymbol symbol) where TTypeSyntax : TypeSyntax { - syntax = syntax.WithPrependedLeadingTrivia(SyntaxFactory.ElasticMarker).WithAppendedTrailingTrivia(SyntaxFactory.ElasticMarker); + syntax = syntax.WithPrependedLeadingTrivia(ElasticMarker).WithAppendedTrailingTrivia(ElasticMarker); syntax = syntax.WithAdditionalAnnotations(SymbolAnnotation.Create(symbol)); return syntax; @@ -91,17 +91,17 @@ public override TypeSyntax VisitArrayType(IArrayTypeSymbol symbol) var arrayType = symbol; while (arrayType != null && !arrayType.Equals(underlyingType)) { - ranks.Add(SyntaxFactory.ArrayRankSpecifier( - [.. Enumerable.Repeat(SyntaxFactory.OmittedArraySizeExpression(), arrayType.Rank)])); + ranks.Add(ArrayRankSpecifier( + [.. Enumerable.Repeat(OmittedArraySizeExpression(), arrayType.Rank)])); arrayType = arrayType.ElementType as IArrayTypeSymbol; } - TypeSyntax arrayTypeSyntax = SyntaxFactory.ArrayType(elementTypeSyntax, [.. ranks]); + TypeSyntax arrayTypeSyntax = ArrayType(elementTypeSyntax, [.. ranks]); if (symbol.NullableAnnotation == NullableAnnotation.Annotated) { - arrayTypeSyntax = SyntaxFactory.NullableType(arrayTypeSyntax); + arrayTypeSyntax = NullableType(arrayTypeSyntax); } return AddInformationTo(arrayTypeSyntax, symbol); @@ -109,9 +109,9 @@ public override TypeSyntax VisitArrayType(IArrayTypeSymbol symbol) public override TypeSyntax VisitDynamicType(IDynamicTypeSymbol symbol) { - var typeSyntax = SyntaxFactory.IdentifierName("dynamic"); + var typeSyntax = IdentifierName("dynamic"); return symbol.NullableAnnotation is NullableAnnotation.Annotated - ? AddInformationTo(SyntaxFactory.NullableType(typeSyntax), symbol) + ? AddInformationTo(NullableType(typeSyntax), symbol) : AddInformationTo(typeSyntax, symbol); } @@ -119,7 +119,7 @@ public static bool TryCreateNativeIntegerType(INamedTypeSymbol symbol, [NotNullW { if (symbol.IsNativeIntegerType) { - syntax = SyntaxFactory.IdentifierName(symbol.SpecialType == SpecialType.System_IntPtr ? "nint" : "nuint"); + syntax = IdentifierName(symbol.SpecialType == SpecialType.System_IntPtr ? "nint" : "nuint"); return true; } @@ -151,24 +151,24 @@ public override TypeSyntax VisitFunctionPointerType(IFunctionPointerTypeSymbol s _ => throw ExceptionUtilities.UnexpectedValue(symbol.Signature.CallingConvention), }; - callingConventionSyntax = SyntaxFactory.FunctionPointerCallingConvention( - SyntaxFactory.Token(SyntaxKind.UnmanagedKeyword), + callingConventionSyntax = FunctionPointerCallingConvention( + UnmanagedKeyword, conventionsList is object - ? SyntaxFactory.FunctionPointerUnmanagedCallingConventionList([.. conventionsList]) + ? FunctionPointerUnmanagedCallingConventionList([.. conventionsList]) : null); static FunctionPointerUnmanagedCallingConventionSyntax GetConventionForString(string identifier) - => SyntaxFactory.FunctionPointerUnmanagedCallingConvention(SyntaxFactory.Identifier(identifier)); + => FunctionPointerUnmanagedCallingConvention(Identifier(identifier)); } var parameters = symbol.Signature.Parameters.Select(p => (p.Type, RefKindModifiers: CSharpSyntaxGeneratorInternal.GetParameterModifiers(p.RefKind))) - .Concat(SpecializedCollections.SingletonEnumerable(( + .Concat([( Type: symbol.Signature.ReturnType, - RefKindModifiers: CSharpSyntaxGeneratorInternal.GetParameterModifiers(symbol.Signature.RefKind, forFunctionPointerReturnParameter: true)))) - .SelectAsArray(t => SyntaxFactory.FunctionPointerParameter(t.Type.GenerateTypeSyntax()).WithModifiers(t.RefKindModifiers)); + RefKindModifiers: CSharpSyntaxGeneratorInternal.GetParameterModifiers(symbol.Signature.RefKind, forFunctionPointerReturnParameter: true))]) + .SelectAsArray(t => FunctionPointerParameter(t.Type.GenerateTypeSyntax()).WithModifiers(t.RefKindModifiers)); return AddInformationTo( - SyntaxFactory.FunctionPointerType(callingConventionSyntax, SyntaxFactory.FunctionPointerParameterList([.. parameters])), symbol); + FunctionPointerType(callingConventionSyntax, FunctionPointerParameterList([.. parameters])), symbol); } public TypeSyntax CreateSimpleTypeSyntax(INamedTypeSymbol symbol) @@ -201,31 +201,31 @@ public TypeSyntax CreateSimpleTypeSyntax(INamedTypeSymbol symbol) } var typeArguments = symbol.IsUnboundGenericType - ? Enumerable.Repeat((TypeSyntax)SyntaxFactory.OmittedTypeArgument(), symbol.TypeArguments.Length) + ? Enumerable.Repeat((TypeSyntax)OmittedTypeArgument(), symbol.TypeArguments.Length) : symbol.TypeArguments.SelectAsArray(t => t.GenerateTypeSyntax()); - return SyntaxFactory.GenericName( + return GenericName( symbol.Name.ToIdentifierToken(), - SyntaxFactory.TypeArgumentList([.. typeArguments])); + TypeArgumentList([.. typeArguments])); } public static QualifiedNameSyntax CreateSystemObject() { - return SyntaxFactory.QualifiedName( - SyntaxFactory.AliasQualifiedName( + return QualifiedName( + AliasQualifiedName( CreateGlobalIdentifier(), - SyntaxFactory.IdentifierName("System")), - SyntaxFactory.IdentifierName("Object")); + IdentifierName("System")), + IdentifierName("Object")); } private static IdentifierNameSyntax CreateGlobalIdentifier() - => SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword)); + => IdentifierName(GlobalKeyword); private static TypeSyntax? TryCreateSpecializedNamedTypeSyntax(INamedTypeSymbol symbol) { if (symbol.SpecialType == SpecialType.System_Void) { - return SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)); + return PredefinedType(VoidKeyword); } if (symbol.IsTupleType && symbol.TupleElements.Length >= 2) @@ -240,7 +240,7 @@ private static IdentifierNameSyntax CreateGlobalIdentifier() if (innerType.TypeKind != TypeKind.Pointer) { return AddInformationTo( - SyntaxFactory.NullableType(innerType.GenerateTypeSyntax()), symbol); + NullableType(innerType.GenerateTypeSyntax()), symbol); } } @@ -253,11 +253,11 @@ private static TupleTypeSyntax CreateTupleTypeSyntax(INamedTypeSymbol symbol) foreach (var element in symbol.TupleElements) { - var name = element.IsImplicitlyDeclared ? default : SyntaxFactory.Identifier(element.Name); - list = list.Add(SyntaxFactory.TupleElement(element.Type.GenerateTypeSyntax(), name)); + var name = element.IsImplicitlyDeclared ? default : Identifier(element.Name); + list = list.Add(TupleElement(element.Type.GenerateTypeSyntax(), name)); } - return AddInformationTo(SyntaxFactory.TupleType(list), symbol); + return AddInformationTo(TupleType(list), symbol); } public override TypeSyntax VisitNamedType(INamedTypeSymbol symbol) @@ -278,7 +278,7 @@ public override TypeSyntax VisitNamedType(INamedTypeSymbol symbol) if (containingTypeSyntax is NameSyntax name) { typeSyntax = AddInformationTo( - SyntaxFactory.QualifiedName(name, simpleNameSyntax), + QualifiedName(name, simpleNameSyntax), symbol); } else @@ -299,7 +299,7 @@ public override TypeSyntax VisitNamedType(INamedTypeSymbol symbol) else { var container = symbol.ContainingNamespace.Accept(this)!; - typeSyntax = AddInformationTo(SyntaxFactory.QualifiedName( + typeSyntax = AddInformationTo(QualifiedName( (NameSyntax)container, simpleNameSyntax), symbol); } @@ -309,7 +309,7 @@ public override TypeSyntax VisitNamedType(INamedTypeSymbol symbol) { // 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); + typeSyntax = AddInformationTo(NullableType(typeSyntax), symbol); } return typeSyntax; @@ -330,7 +330,7 @@ public override TypeSyntax VisitNamespace(INamespaceSymbol symbol) else { var container = symbol.ContainingNamespace.Accept(this)!; - return AddInformationTo(SyntaxFactory.QualifiedName( + return AddInformationTo(QualifiedName( (NameSyntax)container, syntax), symbol); } @@ -343,7 +343,7 @@ public override TypeSyntax VisitNamespace(INamespaceSymbol symbol) private static TypeSyntax AddGlobalAlias(INamespaceOrTypeSymbol symbol, SimpleNameSyntax syntax) { return AddInformationTo( - SyntaxFactory.AliasQualifiedName( + AliasQualifiedName( CreateGlobalIdentifier(), syntax), symbol); } @@ -353,7 +353,7 @@ public override TypeSyntax VisitPointerType(IPointerTypeSymbol symbol) ThrowIfNameOnly(); return AddInformationTo( - SyntaxFactory.PointerType(symbol.PointedAtType.GenerateTypeSyntax()), + PointerType(symbol.PointedAtType.GenerateTypeSyntax()), symbol); } @@ -364,7 +364,7 @@ public override TypeSyntax VisitTypeParameter(ITypeParameterSymbol symbol) { // 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); + typeSyntax = AddInformationTo(NullableType(typeSyntax), symbol); } return typeSyntax; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs index cd4295a86059d..5ad944b1633c9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,6 +17,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions; +using static CSharpSyntaxTokens; +using static SyntaxFactory; + internal static partial class ITypeSymbolExtensions { /// if only normal name-syntax nodes should be returned. @@ -42,7 +43,7 @@ private static TypeSyntax GenerateTypeSyntax( { // something with an anonymous type can only be represented with 'var', regardless // of what the user's preferences might be. - return SyntaxFactory.IdentifierName("var"); + return IdentifierName("var"); } var syntax = containsAnonymousType @@ -74,21 +75,21 @@ public static TypeSyntax GenerateRefTypeSyntax( this INamespaceOrTypeSymbol symbol) { var underlyingType = GenerateTypeSyntax(symbol) - .WithPrependedLeadingTrivia(SyntaxFactory.ElasticMarker) + .WithPrependedLeadingTrivia(ElasticMarker) .WithAdditionalAnnotations(Simplifier.Annotation); - var refKeyword = SyntaxFactory.Token(SyntaxKind.RefKeyword); - return SyntaxFactory.RefType(refKeyword, underlyingType); + var refKeyword = RefKeyword; + return RefType(refKeyword, underlyingType); } public static TypeSyntax GenerateRefReadOnlyTypeSyntax( this INamespaceOrTypeSymbol symbol) { var underlyingType = GenerateTypeSyntax(symbol) - .WithPrependedLeadingTrivia(SyntaxFactory.ElasticMarker) + .WithPrependedLeadingTrivia(ElasticMarker) .WithAdditionalAnnotations(Simplifier.Annotation); - var refKeyword = SyntaxFactory.Token(SyntaxKind.RefKeyword); - var readOnlyKeyword = SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword); - return SyntaxFactory.RefType(refKeyword, readOnlyKeyword, underlyingType); + var refKeyword = RefKeyword; + var readOnlyKeyword = ReadOnlyKeyword; + return RefType(refKeyword, readOnlyKeyword, underlyingType); } public static bool ContainingTypesOrSelfHasUnsafeKeyword(this ITypeSymbol containingType) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs index bb9d23cfe0db2..b15d8d2e2ff26 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs @@ -85,14 +85,14 @@ public static IEnumerable GetAllBaseListTypes(this TypeDeclarati baseListTypes.AddRange(baseTypes); } - return baseListTypes.ToImmutable(); + return baseListTypes.ToImmutableAndClear(); } } if (typeNode.BaseList != null) return typeNode.BaseList.Types; - return SpecializedCollections.EmptyEnumerable(); + return []; } private static SyntaxToken EnsureToken(SyntaxToken token, SyntaxKind kind, bool prependNewLineIfMissing = false, bool appendNewLineIfMissing = false) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpReplaceDiscardDeclarationsWithAssignmentsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpReplaceDiscardDeclarationsWithAssignmentsService.cs index 556f6d97f2316..6de1859a1714d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpReplaceDiscardDeclarationsWithAssignmentsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpReplaceDiscardDeclarationsWithAssignmentsService.cs @@ -14,7 +14,6 @@ using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.ReplaceDiscardDeclarationsWithAssignments; @@ -23,6 +22,8 @@ namespace Microsoft.CodeAnalysis.CSharp.ReplaceDiscardDeclarationsWithAssignments; +using static SyntaxFactory; + [ExportLanguageService(typeof(IReplaceDiscardDeclarationsWithAssignmentsService), LanguageNames.CSharp), Shared] internal sealed class CSharpReplaceDiscardDeclarationsWithAssignmentsService : IReplaceDiscardDeclarationsWithAssignmentsService { @@ -68,13 +69,13 @@ public async Task ReplaceAsync( // "M(out var _)" => "M(out _)" // "M(out int _)" => "M(out _)" - var discardToken = SyntaxFactory.Identifier( + var discardToken = Identifier( leading: declarationExpression.GetLeadingTrivia(), contextualKind: SyntaxKind.UnderscoreToken, text: discardSyntax.UnderscoreToken.Text, valueText: discardSyntax.UnderscoreToken.ValueText, trailing: declarationExpression.GetTrailingTrivia()); - var replacementNode = SyntaxFactory.IdentifierName(discardToken); + var replacementNode = IdentifierName(discardToken); // Removing explicit type is possible only if there are no overloads of the method with same parameter. // For example, if method "M" had overloads with signature "void M(int x)" and "void M(char x)", @@ -95,7 +96,7 @@ public async Task ReplaceAsync( declarationPattern.Parent is IsPatternExpressionSyntax isPatternExpression) { // "x is int _" => "x is int" - var replacementNode = SyntaxFactory.BinaryExpression( + var replacementNode = BinaryExpression( kind: SyntaxKind.IsExpression, left: isPatternExpression.Expression, operatorToken: isPatternExpression.IsKeyword, @@ -211,7 +212,7 @@ private void ProcessDeclarationStatement() } else { - _editor.ReplaceNode(_localDeclarationStatement, SyntaxFactory.Block(_statementsBuilder)); + _editor.ReplaceNode(_localDeclarationStatement, Block(_statementsBuilder)); } } @@ -224,8 +225,8 @@ private void GenerateDeclarationStatementForCurrentNonDiscardVariables() // which are split by a single assignment statement "_ = M();" if (_currentNonDiscardVariables.Count > 0) { - var statement = SyntaxFactory.LocalDeclarationStatement( - SyntaxFactory.VariableDeclaration(_localDeclarationStatement.Declaration.Type, _currentNonDiscardVariables)) + var statement = LocalDeclarationStatement( + VariableDeclaration(_localDeclarationStatement.Declaration.Type, _currentNonDiscardVariables)) .WithAdditionalAnnotations(Formatter.Annotation); _statementsBuilder.Add(statement); _currentNonDiscardVariables = []; @@ -241,10 +242,10 @@ private void GenerateAssignmentForDiscardVariable(VariableDeclaratorSyntax varia if (variable.Initializer != null) { _statementsBuilder.Add( - SyntaxFactory.ExpressionStatement( - SyntaxFactory.AssignmentExpression( + ExpressionStatement( + AssignmentExpression( kind: SyntaxKind.SimpleAssignmentExpression, - left: SyntaxFactory.IdentifierName(variable.Identifier), + left: IdentifierName(variable.Identifier), operatorToken: variable.Initializer.EqualsToken, right: variable.Initializer.Value)) .WithAdditionalAnnotations(Formatter.Annotation)); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxFactsService.cs index 49d7a4303cdec..36ce078826cd3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxFactsService.cs @@ -10,15 +10,15 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.LanguageService; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp; +using static CSharpSyntaxTokens; + internal sealed partial class CSharpSyntaxFactsServiceFactory { private sealed class CSharpSyntaxFactsService : CSharpSyntaxFacts, ISyntaxFactsService @@ -95,7 +95,7 @@ public override SyntaxNode Visit(SyntaxNode node) { if (!_addedFirstCloseCurly) { - var closeBrace = SyntaxFactory.Token(SyntaxKind.CloseBraceToken) + var closeBrace = CloseBraceToken .WithAdditionalAnnotations(Formatter.Annotation); rewritten = rewritten.ReplaceToken(braces.closeBrace, closeBrace); _addedFirstCloseCurly = true; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs index b8f3e3af262d1..e92cff2ad9f08 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs @@ -17,6 +17,8 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using static CSharpSyntaxTokens; + [ExportLanguageService(typeof(SyntaxGeneratorInternal), LanguageNames.CSharp), Shared] internal sealed class CSharpSyntaxGeneratorInternal : SyntaxGeneratorInternal { @@ -36,7 +38,7 @@ public override SyntaxTrivia EndOfLine(string text) public override SyntaxNode LocalDeclarationStatement(SyntaxNode? type, SyntaxToken name, SyntaxNode? initializer, bool isConst) { return SyntaxFactory.LocalDeclarationStatement( - isConst ? [SyntaxFactory.Token(SyntaxKind.ConstKeyword)] : default, + isConst ? [ConstKeyword] : default, VariableDeclaration(type, name, initializer)); } @@ -104,11 +106,11 @@ public override SyntaxNode Interpolation(SyntaxNode syntaxNode) => SyntaxFactory.Interpolation((ExpressionSyntax)syntaxNode); public override SyntaxNode InterpolationAlignmentClause(SyntaxNode alignment) - => SyntaxFactory.InterpolationAlignmentClause(SyntaxFactory.Token(SyntaxKind.CommaToken), (ExpressionSyntax)alignment); + => SyntaxFactory.InterpolationAlignmentClause(CommaToken, (ExpressionSyntax)alignment); public override SyntaxNode InterpolationFormatClause(string format) => SyntaxFactory.InterpolationFormatClause( - SyntaxFactory.Token(SyntaxKind.ColonToken), + ColonToken, SyntaxFactory.Token(default, SyntaxKind.InterpolatedStringTextToken, format, format, default)); public override SyntaxNode TypeParameterList(IEnumerable typeParameterNames) @@ -118,14 +120,14 @@ internal static SyntaxTokenList GetParameterModifiers(RefKind refKind, bool forF => refKind switch { RefKind.None => [], - RefKind.Out => [SyntaxFactory.Token(SyntaxKind.OutKeyword)], - RefKind.Ref => [SyntaxFactory.Token(SyntaxKind.RefKeyword)], + RefKind.Out => [OutKeyword], + RefKind.Ref => [RefKeyword], // Note: RefKind.RefReadonly == RefKind.In. Function Pointers must use the correct // ref kind syntax when generating for the return parameter vs other parameters. // The return parameter must use ref readonly, like regular methods. - RefKind.In when !forFunctionPointerReturnParameter => [SyntaxFactory.Token(SyntaxKind.InKeyword)], - RefKind.RefReadOnly when forFunctionPointerReturnParameter => [SyntaxFactory.Token(SyntaxKind.RefKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)], - RefKind.RefReadOnlyParameter => [SyntaxFactory.Token(SyntaxKind.RefKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)], + RefKind.In when !forFunctionPointerReturnParameter => [InKeyword], + RefKind.RefReadOnly when forFunctionPointerReturnParameter => [RefKeyword, ReadOnlyKeyword], + RefKind.RefReadOnlyParameter => [RefKeyword, ReadOnlyKeyword], _ => throw ExceptionUtilities.UnexpectedValue(refKind), }; @@ -151,7 +153,7 @@ public override bool SupportsPatterns(ParseOptions options) public override SyntaxNode IsPatternExpression(SyntaxNode expression, SyntaxToken isKeyword, SyntaxNode pattern) => SyntaxFactory.IsPatternExpression( (ExpressionSyntax)expression, - isKeyword == default ? SyntaxFactory.Token(SyntaxKind.IsKeyword) : isKeyword, + isKeyword == default ? IsKeyword : isKeyword, (PatternSyntax)pattern); public override SyntaxNode AndPattern(SyntaxNode left, SyntaxNode right) @@ -166,19 +168,19 @@ public override SyntaxNode DeclarationPattern(INamedTypeSymbol type, string name SyntaxFactory.SingleVariableDesignation(name.ToIdentifierToken())); public override SyntaxNode LessThanRelationalPattern(SyntaxNode expression) - => SyntaxFactory.RelationalPattern(SyntaxFactory.Token(SyntaxKind.LessThanToken), (ExpressionSyntax)expression); + => SyntaxFactory.RelationalPattern(LessThanToken, (ExpressionSyntax)expression); public override SyntaxNode LessThanEqualsRelationalPattern(SyntaxNode expression) - => SyntaxFactory.RelationalPattern(SyntaxFactory.Token(SyntaxKind.LessThanEqualsToken), (ExpressionSyntax)expression); + => SyntaxFactory.RelationalPattern(LessThanEqualsToken, (ExpressionSyntax)expression); public override SyntaxNode GreaterThanRelationalPattern(SyntaxNode expression) - => SyntaxFactory.RelationalPattern(SyntaxFactory.Token(SyntaxKind.GreaterThanToken), (ExpressionSyntax)expression); + => SyntaxFactory.RelationalPattern(GreaterThanToken, (ExpressionSyntax)expression); public override SyntaxNode GreaterThanEqualsRelationalPattern(SyntaxNode expression) - => SyntaxFactory.RelationalPattern(SyntaxFactory.Token(SyntaxKind.GreaterThanEqualsToken), (ExpressionSyntax)expression); + => SyntaxFactory.RelationalPattern(GreaterThanEqualsToken, (ExpressionSyntax)expression); public override SyntaxNode NotPattern(SyntaxNode pattern) - => SyntaxFactory.UnaryPattern(SyntaxFactory.Token(SyntaxKind.NotKeyword), (PatternSyntax)Parenthesize(pattern)); + => SyntaxFactory.UnaryPattern(NotKeyword, (PatternSyntax)Parenthesize(pattern)); public override SyntaxNode OrPattern(SyntaxNode left, SyntaxNode right) => SyntaxFactory.BinaryPattern(SyntaxKind.OrPattern, (PatternSyntax)Parenthesize(left), (PatternSyntax)Parenthesize(right)); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs index 6fb25a485e677..a44bd6b9e8deb 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs @@ -87,7 +87,7 @@ private IEnumerable GetTypesComplex(SyntaxNode node) } // TODO(cyrusn): More cases if necessary. - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable GetTypesSimple(SyntaxNode node) @@ -114,13 +114,11 @@ private IEnumerable GetTypesSimple(SyntaxNode node) } if (IsUsableTypeFunc(typeInferenceInfo)) - { - return SpecializedCollections.SingletonEnumerable(typeInferenceInfo); - } + return [typeInferenceInfo]; } } - return SpecializedCollections.EmptyEnumerable(); + return []; } protected override IEnumerable InferTypesWorker_DoNotCallDirectly( @@ -185,7 +183,7 @@ protected override IEnumerable InferTypesWorker_DoNotCallDire WhenClauseSyntax whenClause => InferTypeInWhenClause(whenClause), WhileStatementSyntax whileStatement => InferTypeInWhileStatement(whileStatement), YieldStatementSyntax yieldStatement => InferTypeInYieldStatement(yieldStatement), - _ => SpecializedCollections.EmptyEnumerable(), + _ => [], }; } @@ -252,7 +250,7 @@ protected override IEnumerable InferTypesWorker_DoNotCallDire WhenClauseSyntax whenClause => InferTypeInWhenClause(whenClause, token), WhileStatementSyntax whileStatement => InferTypeInWhileStatement(whileStatement, token), YieldStatementSyntax yieldStatement => InferTypeInYieldStatement(yieldStatement, token), - _ => SpecializedCollections.EmptyEnumerable(), + _ => [], }; } @@ -263,7 +261,7 @@ private IEnumerable InferTypeInAnonymousObjectCreation(Anonym return InferTypes(expression.SpanStart); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInArgument( @@ -273,9 +271,7 @@ private IEnumerable InferTypeInArgument( { // If we have a position, then it must be after the colon in a named argument. if (argument.NameColon == null || argument.NameColon.ColonToken != previousToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; } if (argument.Parent != null) @@ -334,7 +330,7 @@ private IEnumerable InferTypeInArgument( } } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInTupleExpression( @@ -350,7 +346,7 @@ private IEnumerable InferTypeInTupleExpression( return InferTypeInTupleExpression(tupleExpression, (ArgumentSyntax)argsAndCommas[commaIndex + 1]); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInTupleExpression( @@ -371,9 +367,7 @@ private IEnumerable InferTypeInAttributeArgument(AttributeArg { // If we have a position, then it must be after the colon or equals in an argument. if (argument.NameColon == null || argument.NameColon.ColonToken != previousToken || argument.NameEquals.EqualsToken != previousToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; } if (argument.Parent != null) @@ -385,7 +379,7 @@ private IEnumerable InferTypeInAttributeArgument(AttributeArg } } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInConstructorInitializer(ConstructorInitializerSyntax initializer, int index, ArgumentSyntax argument = null) @@ -440,9 +434,7 @@ private IEnumerable InferTypeInObjectCreationExpression(BaseO var info = SemanticModel.GetTypeInfo(creation, CancellationToken); if (info.Type is not INamedTypeSymbol type) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (type.TypeKind == TypeKind.Delegate) { @@ -504,9 +496,7 @@ private IEnumerable InferTypeInArgumentList(ArgumentListSynta { // Has to follow the ( or a , if (previousToken != argumentList.OpenParenToken && previousToken.Kind() != SyntaxKind.CommaToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; switch (argumentList.Parent) { @@ -529,16 +519,14 @@ private IEnumerable InferTypeInArgumentList(ArgumentListSynta } } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInAttributeArgumentList(AttributeArgumentListSyntax attributeArgumentList, SyntaxToken previousToken) { // Has to follow the ( or a , if (previousToken != attributeArgumentList.OpenParenToken && previousToken.Kind() != SyntaxKind.CommaToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (attributeArgumentList.Parent is AttributeSyntax attribute) { @@ -546,7 +534,7 @@ private IEnumerable InferTypeInAttributeArgumentList(Attribut return InferTypeInAttribute(attribute, index); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInAttribute(AttributeSyntax attribute, int index, AttributeArgumentSyntax argumentOpt = null) @@ -808,7 +796,7 @@ private IEnumerable InferTypeInArrayCreationExpression( if (previousToken.HasValue && previousToken.Value != arrayCreationExpression.NewKeyword) { // Has to follow the 'new' keyword. - return SpecializedCollections.EmptyEnumerable(); + return []; } if (previousToken.HasValue && previousToken.Value.GetPreviousToken().Kind() == SyntaxKind.EqualsToken) @@ -835,9 +823,7 @@ private IEnumerable InferTypeInArrayRankSpecifier(ArrayRankSp // If we have a token, and it's not the open bracket or one of the commas, then no // inference. if (previousToken == arrayRankSpecifier.CloseBracketToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(this.Compilation.GetSpecialType(SpecialType.System_Int32)); } @@ -847,7 +833,7 @@ private IEnumerable InferTypeInArrayType(ArrayTypeSyntax arra if (previousToken.HasValue) { // TODO(cyrusn): NYI. Handle this appropriately if we need to. - return SpecializedCollections.EmptyEnumerable(); + return []; } // Bind the array type, then unwrap whatever we get back based on the number of rank @@ -869,9 +855,7 @@ private IEnumerable InferTypeInAttributeDeclaration(Attribute { // If we have a position, then it has to be after the open bracket. if (previousToken.HasValue && previousToken.Value != attributeDeclaration.OpenBracketToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(this.Compilation.AttributeType()); } @@ -882,9 +866,7 @@ private IEnumerable InferTypeInAttributeTargetSpecifier( { // If we have a position, then it has to be after the colon. if (previousToken.HasValue && previousToken.Value != attributeTargetSpecifier.ColonToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(this.Compilation.AttributeType()); } @@ -893,9 +875,7 @@ private IEnumerable InferTypeInBracketedArgumentList(Brackete { // Has to follow the [ or a , if (previousToken != bracketedArgumentList.OpenBracketToken && previousToken.Kind() != SyntaxKind.CommaToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (bracketedArgumentList.Parent is ElementAccessExpressionSyntax elementAccess) { @@ -904,7 +884,7 @@ private IEnumerable InferTypeInBracketedArgumentList(Brackete elementAccess, index); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private static int GetArgumentListIndex(BaseArgumentListSyntax argumentList, SyntaxToken previousToken) @@ -1077,21 +1057,17 @@ SyntaxKind.CaretToken or return CreateResult(SpecialType.System_Boolean); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInCastExpression(CastExpressionSyntax castExpression, ExpressionSyntax expressionOpt = null, SyntaxToken? previousToken = null) { if (expressionOpt != null && castExpression.Expression != expressionOpt) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; // If we have a position, then it has to be after the close paren. if (previousToken.HasValue && previousToken.Value != castExpression.CloseParenToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return this.GetTypes(castExpression.Type); } @@ -1100,9 +1076,7 @@ private IEnumerable InferTypeInCatchDeclaration(CatchDeclarat { // If we have a position, it has to be after "catch(" if (previousToken.HasValue && previousToken.Value != catchDeclaration.OpenParenToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(this.Compilation.ExceptionType()); } @@ -1111,9 +1085,7 @@ private IEnumerable InferTypeInCatchFilterClause(CatchFilterC { // If we have a position, it has to be after "if (" if (previousToken.HasValue && previousToken.Value != catchFilterClause.OpenParenToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(SpecialType.System_Boolean); } @@ -1198,7 +1170,7 @@ private IEnumerable InferTypeInConditionalExpression(Conditio ? GetTypes(conditional.WhenFalse) : inFalseClause ? GetTypes(conditional.WhenTrue) - : SpecializedCollections.EmptyEnumerable(); + : []; return otherTypes.IsEmpty() ? InferTypes(conditional) @@ -1212,9 +1184,7 @@ private IEnumerable InferTypeInDoStatement(DoStatementSyntax { // If we have a position, we need to be after "do { } while(" if (previousToken.HasValue && previousToken.Value != doStatement.OpenParenToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(SpecialType.System_Boolean); } @@ -1223,7 +1193,7 @@ private IEnumerable InferTypeInEqualsValueClause(EqualsValueC { // If we have a position, it has to be after the = if (previousToken.HasValue && previousToken.Value != equalsValue.EqualsToken) - return SpecializedCollections.EmptyEnumerable(); + return []; if (equalsValue?.Parent is VariableDeclaratorSyntax varDecl) return InferTypeInVariableDeclarator(varDecl); @@ -1237,7 +1207,7 @@ private IEnumerable InferTypeInEqualsValueClause(EqualsValueC return CreateResult(parameter.Type); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInPropertyDeclaration(PropertyDeclarationSyntax propertyDeclaration) @@ -1253,9 +1223,7 @@ private IEnumerable InferTypeInExpressionStatement(SyntaxToke // If we're position based, then that means we're after the semicolon. In this case // we don't have any sort of type to infer. if (previousToken.HasValue) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(SpecialType.System_Void); } @@ -1264,14 +1232,10 @@ private IEnumerable InferTypeInForEachStatement(ForEachStatem { // If we have a position, then we have to be after "foreach(... in" if (previousToken.HasValue && previousToken.Value != forEachStatementSyntax.InKeyword) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (expressionOpt != null && expressionOpt != forEachStatementSyntax.Expression) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var enumerableType = forEachStatementSyntax.AwaitKeyword == default ? this.Compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T) @@ -1295,14 +1259,10 @@ private IEnumerable InferTypeInForStatement(ForStatementSynta { // If we have a position, it has to be after "for(...;" if (previousToken.HasValue && previousToken.Value != forStatement.FirstSemicolonToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (expressionOpt != null && forStatement.Condition != expressionOpt) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(SpecialType.System_Boolean); } @@ -1311,9 +1271,7 @@ private IEnumerable InferTypeInIfStatement(IfStatementSyntax { // If we have a position, we have to be after the "if(" if (previousToken.HasValue && previousToken.Value != ifStatement.OpenParenToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(SpecialType.System_Boolean); } @@ -1466,7 +1424,7 @@ private IEnumerable InferTypeInInitializerExpression( } } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInRecursivePattern(RecursivePatternSyntax recursivePattern) @@ -1515,7 +1473,7 @@ private IEnumerable InferTypeInSubpattern( return result.ToImmutableAndClear(); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeForSingleVariableDesignation(SingleVariableDesignationSyntax singleVariableDesignation) @@ -1534,7 +1492,7 @@ private IEnumerable InferTypeForSingleVariableDesignation(Sin } } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInIsPatternExpression( @@ -1550,7 +1508,7 @@ private IEnumerable InferTypeInIsPatternExpression( return GetTypes(isPatternExpression.Expression); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable GetPatternTypes(PatternSyntax pattern) @@ -1567,7 +1525,7 @@ _ when SemanticModel.GetOperation(pattern, CancellationToken) is IPatternOperati CreateResult(patternOperation.NarrowedType.IsErrorType() ? patternOperation.InputType : patternOperation.NarrowedType), - _ => SpecializedCollections.EmptyEnumerable() + _ => [], }; } @@ -1599,9 +1557,7 @@ private IEnumerable GetTypesForRecursivePattern(RecursivePatt var patternType = GetPatternTypes(subPattern.Pattern).FirstOrDefault(); if (patternType.InferredType == null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; elementTypesBuilder.Add(patternType.InferredType); } @@ -1614,7 +1570,7 @@ private IEnumerable GetTypesForRecursivePattern(RecursivePatt } } - return SpecializedCollections.EmptyEnumerable(); + return []; } private static ImmutableArray GetNullableAnnotations(ImmutableArray elementTypes) @@ -1624,9 +1580,7 @@ private IEnumerable InferTypeInLockStatement(LockStatementSyn { // If we're position based, then we have to be after the "lock(" if (previousToken.HasValue && previousToken.Value != lockStatement.OpenParenToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(SpecialType.System_Object); } @@ -1635,9 +1589,7 @@ private IEnumerable InferTypeInLambdaExpression(LambdaExpress { // If we have a position, it has to be after the lambda arrow. if (previousToken.HasValue && previousToken.Value != lambdaExpression.ArrowToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return InferTypeInAnonymousFunctionExpression(lambdaExpression); } @@ -1655,12 +1607,11 @@ private IEnumerable InferTypeInAnonymousFunctionExpression(An if (invoke != null) { var isAsync = anonymousFunction.AsyncKeyword.Kind() != SyntaxKind.None; - return SpecializedCollections.SingletonEnumerable( - new TypeInferenceInfo(UnwrapTaskLike(invoke.ReturnType, isAsync))); + return [new TypeInferenceInfo(UnwrapTaskLike(invoke.ReturnType, isAsync))]; } } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInMemberDeclarator(AnonymousObjectMemberDeclaratorSyntax memberDeclarator, SyntaxToken? previousTokenOpt = null) @@ -1669,9 +1620,7 @@ private IEnumerable InferTypeInMemberDeclarator(AnonymousObje { // If we're position based, then we have to be after the = if (previousTokenOpt.HasValue && previousTokenOpt.Value != memberDeclarator.NameEquals.EqualsToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var types = InferTypes((AnonymousObjectCreationExpressionSyntax)memberDeclarator.Parent); @@ -1681,7 +1630,7 @@ private IEnumerable InferTypeInMemberDeclarator(AnonymousObje .Select(p => new TypeInferenceInfo(p.Type))); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInNameColon(NameColonSyntax nameColon, SyntaxToken previousToken) @@ -1689,14 +1638,14 @@ private IEnumerable InferTypeInNameColon(NameColonSyntax name if (previousToken != nameColon.ColonToken) { // Must follow the colon token. - return SpecializedCollections.EmptyEnumerable(); + return []; } return nameColon.Parent switch { ArgumentSyntax argumentSyntax => InferTypeInArgument(argumentSyntax), SubpatternSyntax subPattern => InferTypeInSubpattern(subPattern, subPattern.Pattern), - _ => SpecializedCollections.EmptyEnumerable() + _ => [], }; } @@ -1705,13 +1654,13 @@ private IEnumerable InferTypeInExpressionColon(ExpressionColo if (previousToken != expressionColon.ColonToken) { // Must follow the colon token. - return SpecializedCollections.EmptyEnumerable(); + return []; } return expressionColon.Parent switch { SubpatternSyntax subPattern => InferTypeInSubpattern(subPattern, subPattern.Pattern), - _ => SpecializedCollections.EmptyEnumerable() + _ => [], }; } @@ -1728,9 +1677,7 @@ private IEnumerable InferTypeInMemberAccessExpression( if (previousToken != null) { if (previousToken.Value != memberAccessExpression.OperatorToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; // We're right after the dot in "Goo.Bar". The type for "Bar" should be // whatever type we'd infer for "Goo.Bar" itself. @@ -1812,7 +1759,7 @@ private IEnumerable InferTypeForExpressionOfMemberAccessExpre } } - return SpecializedCollections.EmptyEnumerable(); + return []; } private ITypeSymbol InferTypeForFirstParameterOfLambda( @@ -1890,7 +1837,7 @@ private IEnumerable InferTypeInNameColon(NameColonSyntax name return GetPatternTypes(subpattern.Pattern); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInExpressionColon(ExpressionColonSyntax expressionColon) @@ -1900,7 +1847,7 @@ private IEnumerable InferTypeInExpressionColon(ExpressionColo return GetPatternTypes(subpattern.Pattern); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInNameEquals(NameEqualsSyntax nameEquals, SyntaxToken? previousToken = null) @@ -1918,16 +1865,14 @@ private IEnumerable InferTypeInNameEquals(NameEqualsSyntax na return this.GetTypes(argumentExpression); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInPostfixUnaryExpression(PostfixUnaryExpressionSyntax postfixUnaryExpressionSyntax, SyntaxToken? previousToken = null) { // If we're after a postfix ++ or -- then we can't infer anything. if (previousToken.HasValue) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; switch (postfixUnaryExpressionSyntax.Kind()) { @@ -1936,7 +1881,7 @@ private IEnumerable InferTypeInPostfixUnaryExpression(Postfix return CreateResult(this.Compilation.GetSpecialType(SpecialType.System_Int32)); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInPrefixUnaryExpression(PrefixUnaryExpressionSyntax prefixUnaryExpression, SyntaxToken? previousToken = null) @@ -1973,7 +1918,7 @@ private IEnumerable InferTypeInPrefixUnaryExpression(PrefixUn return InferTypeInAddressOfExpression(prefixUnaryExpression); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInAddressOfExpression(PrefixUnaryExpressionSyntax prefixUnaryExpression) @@ -2007,9 +1952,7 @@ private IEnumerable InferTypeInAwaitExpression(AwaitExpressio var taskOfT = this.Compilation.TaskOfTType(); if (task == null || taskOfT == null) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; if (!types.Any()) { @@ -2023,9 +1966,7 @@ private IEnumerable InferTypeInYieldStatement(YieldStatementS { // If we are position based, then we have to be after the return keyword if (previousToken.HasValue && (previousToken.Value != yieldStatement.ReturnOrBreakKeyword || yieldStatement.ReturnOrBreakKeyword.IsKind(SyntaxKind.BreakKeyword))) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var declaration = yieldStatement.FirstAncestorOrSelf(n => n.IsReturnableConstruct()); var memberSymbol = GetDeclaredMemberSymbolFromOriginalSemanticModel(declaration); @@ -2035,8 +1976,8 @@ private IEnumerable InferTypeInYieldStatement(YieldStatementS // We don't care what the type is, as long as it has 1 type argument. This will work for IEnumerable, IEnumerator, // IAsyncEnumerable, IAsyncEnumerator and it's also good for error recovery in case there is a missing using. return memberType is INamedTypeSymbol namedType && namedType.TypeArguments.Length == 1 - ? SpecializedCollections.SingletonEnumerable(new TypeInferenceInfo(namedType.TypeArguments[0])) - : SpecializedCollections.EmptyEnumerable(); + ? [new TypeInferenceInfo(namedType.TypeArguments[0])] + : []; } private IEnumerable InferTypeInRefExpression(RefExpressionSyntax refExpression) @@ -2066,9 +2007,7 @@ private IEnumerable InferTypeForReturnStatement( { // If we are position based, then we have to be after the return statement. if (previousToken.HasValue && previousToken.Value != returnStatement.ReturnKeyword) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var ancestor = returnStatement.FirstAncestorOrSelf(n => n.IsReturnableConstruct()); @@ -2089,8 +2028,8 @@ private IEnumerable InferTypeInMethodLikeDeclaration(SyntaxNo var isAsync = symbol is IMethodSymbol methodSymbol && methodSymbol.IsAsync; return type != null - ? SpecializedCollections.SingletonEnumerable(new TypeInferenceInfo(UnwrapTaskLike(type, isAsync))) - : SpecializedCollections.EmptyEnumerable(); + ? [new TypeInferenceInfo(UnwrapTaskLike(type, isAsync))] + : []; } private ISymbol GetDeclaredMemberSymbolFromOriginalSemanticModel(SyntaxNode declarationInCurrentTree) @@ -2139,7 +2078,7 @@ private IEnumerable InferTypeInSwitchExpressionArm( return InferTypes(switchExpression); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInSwitchExpression(SwitchExpressionSyntax switchExpression, SyntaxToken token) @@ -2147,7 +2086,7 @@ private IEnumerable InferTypeInSwitchExpression(SwitchExpress if (token.Kind() is SyntaxKind.OpenBraceToken or SyntaxKind.CommaToken) return GetTypes(switchExpression.GoverningExpression); - return SpecializedCollections.EmptyEnumerable(); + return []; } private IEnumerable InferTypeInSwitchLabel( @@ -2158,7 +2097,7 @@ private IEnumerable InferTypeInSwitchLabel( if (previousToken.Value != switchLabel.Keyword || switchLabel.Kind() != SyntaxKind.CaseSwitchLabel) { - return SpecializedCollections.EmptyEnumerable(); + return []; } } @@ -2171,9 +2110,7 @@ private IEnumerable InferTypeInSwitchStatement( { // If we have a position, then it has to be after "switch(" if (previousToken.HasValue && previousToken.Value != switchStatement.OpenParenToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; // Use the first case label to determine the return type. if (switchStatement.Sections.SelectMany(ss => ss.Labels) @@ -2193,9 +2130,7 @@ private IEnumerable InferTypeInThrowExpression(ThrowExpressio { // If we have a position, it has to be after the 'throw' keyword. if (previousToken.HasValue && previousToken.Value != throwExpression.ThrowKeyword) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(this.Compilation.ExceptionType()); } @@ -2204,9 +2139,7 @@ private IEnumerable InferTypeInThrowStatement(ThrowStatementS { // If we have a position, it has to be after the 'throw' keyword. if (previousToken.HasValue && previousToken.Value != throwStatement.ThrowKeyword) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(this.Compilation.ExceptionType()); } @@ -2215,9 +2148,7 @@ private IEnumerable InferTypeInUsingStatement(UsingStatementS { // If we have a position, it has to be after "using(" if (previousToken.HasValue && previousToken.Value != usingStatement.OpenParenToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(SpecialType.System_IDisposable); } @@ -2226,11 +2157,11 @@ private IEnumerable InferTypeInVariableDeclarator(VariableDec { var variableType = variableDeclarator.GetVariableType(); if (variableType == null) - return SpecializedCollections.EmptyEnumerable(); + return []; var symbol = SemanticModel.GetDeclaredSymbol(variableDeclarator); if (symbol == null) - return SpecializedCollections.EmptyEnumerable(); + return []; var type = symbol.GetSymbolType(); var types = CreateResult(type).Where(IsUsableTypeFunc); @@ -2308,8 +2239,8 @@ declExpr.Designation is ParenthesizedVariableDesignationSyntax parenthesizedVari return inferredFutureUsage.Length > 0 ? inferredFutureUsage[0].InferredType : Compilation.ObjectType; }); - return SpecializedCollections.SingletonEnumerable(new TypeInferenceInfo( - Compilation.CreateTupleTypeSymbol(elementTypes, elementNames))); + return [new TypeInferenceInfo( + Compilation.CreateTupleTypeSymbol(elementTypes, elementNames))]; } return GetTypes(declExpr.Type); @@ -2327,7 +2258,7 @@ declExpr.Designation is ParenthesizedVariableDesignationSyntax parenthesizedVari return CreateResult(tupleType); } - return SpecializedCollections.EmptyEnumerable(); + return []; } private ITypeSymbol GetTupleType( @@ -2422,20 +2353,16 @@ private IEnumerable InferTypeInWhenClause(WhenClauseSyntax wh { // If we have a position, we have to be after the "when" if (previousToken.HasValue && previousToken.Value != whenClause.WhenKeyword) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; - return SpecializedCollections.SingletonEnumerable(new TypeInferenceInfo(Compilation.GetSpecialType(SpecialType.System_Boolean))); + return [new TypeInferenceInfo(Compilation.GetSpecialType(SpecialType.System_Boolean))]; } private IEnumerable InferTypeInWhileStatement(WhileStatementSyntax whileStatement, SyntaxToken? previousToken = null) { // If we're position based, then we have to be after the "while(" if (previousToken.HasValue && previousToken.Value != whileStatement.OpenParenToken) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; return CreateResult(SpecialType.System_Boolean); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/ForkingSyntaxEditorBasedCodeFixProvider.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/ForkingSyntaxEditorBasedCodeFixProvider.cs index 93c7b6d695911..dda3054036db9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/ForkingSyntaxEditorBasedCodeFixProvider.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/ForkingSyntaxEditorBasedCodeFixProvider.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CodeFixes; @@ -63,7 +64,7 @@ protected sealed override async Task FixAllAsync( { var originalRoot = editor.OriginalRoot; - var originalNodes = new Stack<(TDiagnosticNode diagnosticNode, Diagnostic diagnostic)>(); + using var _ = ArrayBuilder<(TDiagnosticNode diagnosticNode, Diagnostic diagnostic)>.GetInstance(out var originalNodes); foreach (var diagnostic in diagnostics) { var diagnosticNode = (TDiagnosticNode)originalRoot.FindNode( @@ -79,9 +80,9 @@ protected sealed override async Task FixAllAsync( document.WithSyntaxRoot(originalRoot.TrackNodes(originalNodes.Select(static t => t.diagnosticNode))), cancellationToken).ConfigureAwait(false); - while (originalNodes.Count > 0) + while (originalNodes.TryPop(out var tuple)) { - var (originalDiagnosticNode, diagnostic) = originalNodes.Pop(); + var (originalDiagnosticNode, diagnostic) = tuple; var currentRoot = semanticDocument.Root; var diagnosticNode = currentRoot.GetCurrentNodes(originalDiagnosticNode).Single(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/AbstractCodeGenerationService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/AbstractCodeGenerationService.cs index 2da850bdcc8d9..9d3cb3850ad9c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/AbstractCodeGenerationService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/AbstractCodeGenerationService.cs @@ -557,9 +557,7 @@ protected static SyntaxTokenList GetUpdatedDeclarationAccessibilityModifiers( if (isAccessibilityModifier(modifier)) { if (newModifierTokens.Count == 0) - { continue; - } newModifier = newModifierTokens[0] .WithLeadingTrivia(modifier.LeadingTrivia) @@ -584,15 +582,13 @@ protected static SyntaxTokenList GetUpdatedDeclarationAccessibilityModifiers( if (!anyAccessModifierSeen) { for (var i = newModifierTokens.Count - 1; i >= 0; i--) - { updatedModifiersList.Insert(0, newModifierTokens[i]); - } } else { updatedModifiersList.AddRange(newModifierTokens); } - return updatedModifiersList.ToSyntaxTokenList(); + return [.. updatedModifiersList]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationContext.cs index d1f79e57ab467..75ab6c7c49581 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationContext.cs @@ -157,7 +157,7 @@ public CodeGenerationContext( AfterThisLocation = afterThisLocation; BeforeThisLocation = beforeThisLocation; AddImports = addImports; - AdditionalImports = additionalImports ?? SpecializedCollections.EmptyEnumerable(); + AdditionalImports = additionalImports ?? []; GenerateMembers = generateMembers; MergeNestedNamespaces = mergeNestedNamespaces; MergeAttributes = mergeAttributes; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs index b4845a4b392df..3839eb370ebbc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs @@ -30,10 +30,7 @@ internal static class CodeGenerationHelpers return null; } - var exceptionCreationExpression = factory.ObjectCreationExpression( - exceptionType, - SpecializedCollections.EmptyList()); - + var exceptionCreationExpression = factory.ObjectCreationExpression(exceptionType, arguments: []); return factory.ThrowStatement(exceptionCreationExpression); } @@ -74,7 +71,7 @@ public static void GetNameAndInnermostNamespace( break; } - name = string.Join(".", names.ToArray()); + name = string.Join(".", [.. names]); } else { @@ -339,7 +336,7 @@ public static int GetInsertionIndex( // The list was grouped (by type, staticness, accessibility). Try to find a location // to put the new declaration into. - var result = Array.BinarySearch(declarationList.ToArray(), declaration, comparerWithoutNameCheck); + var result = Array.BinarySearch([.. declarationList], declaration, comparerWithoutNameCheck); var desiredGroupIndex = result < 0 ? ~result : result; Debug.Assert(desiredGroupIndex >= 0); Debug.Assert(desiredGroupIndex <= declarationList.Count); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs index 5d525a7b31c10..45c058621bda5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs @@ -109,7 +109,7 @@ public virtual ImmutableArray ReturnTypeCustomModifiers public ImmutableArray UnmanagedCallingConventionTypes => []; public IMethodSymbol Construct(params ITypeSymbol[] typeArguments) - => new CodeGenerationConstructedMethodSymbol(this, typeArguments.ToImmutableArray()); + => new CodeGenerationConstructedMethodSymbol(this, [.. typeArguments]); public IMethodSymbol Construct(ImmutableArray typeArguments, ImmutableArray typeArgumentNullableAnnotations) => new CodeGenerationConstructedMethodSymbol(this, typeArguments); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs index 5b06942521e2d..445328ff50423 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs @@ -63,7 +63,7 @@ public INamedTypeSymbol Construct(params ITypeSymbol[] typeArguments) } return new CodeGenerationConstructedNamedTypeSymbol( - ConstructedFrom, typeArguments.ToImmutableArray(), this.TypeMembers); + ConstructedFrom, [.. typeArguments], this.TypeMembers); } public INamedTypeSymbol Construct(ImmutableArray typeArguments, ImmutableArray typeArgumentNullableAnnotations) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationNamespaceInfo.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationNamespaceInfo.cs index cda9fc67920fb..5b61c1cca86ff 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationNamespaceInfo.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationNamespaceInfo.cs @@ -38,8 +38,6 @@ public static IList GetImports(INamespaceSymbol @namespace) private static IList GetImports(CodeGenerationNamespaceInfo info) { - return info == null - ? SpecializedCollections.EmptyList() - : info._imports; + return info == null ? [] : info._imports; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Editing/AddParameterEditor.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Editing/AddParameterEditor.cs index 7698602175d9b..f77701cbf1c55 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Editing/AddParameterEditor.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Editing/AddParameterEditor.cs @@ -145,7 +145,7 @@ private static ImmutableArray GetDesiredLeadingIndentation( triviaList.Add(lastWhitespace); } - return triviaList.ToImmutable(); + return triviaList.ToImmutableAndClear(); } private static bool ShouldPlaceParametersOnNewLine( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs index 2d2c83b236f21..d7d5fa8da2911 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs @@ -41,6 +41,21 @@ public static async ValueTask GetRequiredSemanticModelAsync(this return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } +#if !CODE_STYLE + + public static async ValueTask GetRequiredNullableDisabledSemanticModelAsync(this Document document, CancellationToken cancellationToken) + { + if (document.TryGetNullableDisabledSemanticModel(out var semanticModel)) + return semanticModel; + +#pragma warning disable RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + semanticModel = await document.GetSemanticModelAsync(SemanticModelOptions.DisableNullableAnalysis, cancellationToken).ConfigureAwait(false); +#pragma warning restore RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); + } + +#endif + public static async ValueTask GetRequiredSyntaxTreeAsync(this Document document, CancellationToken cancellationToken) { if (document.TryGetSyntaxTree(out var syntaxTree)) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions.cs index a728bea95352a..224eef7fde58f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/SyntaxGeneratorExtensions.cs @@ -40,8 +40,8 @@ private static SyntaxNode CreateNewNotImplementedException(SyntaxGenerator codeD : codeDefinitionFactory.QualifiedName(codeDefinitionFactory.IdentifierName(nameof(System)), codeDefinitionFactory.IdentifierName(nameof(NotImplementedException))); return codeDefinitionFactory.ObjectCreationExpression( - notImplementedExceptionTypeSyntax, - SpecializedCollections.EmptyList()); + notImplementedExceptionTypeSyntax, + arguments: []); } public static ImmutableArray CreateThrowNotImplementedStatementBlock( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs index fcf86c82b58ca..bc4197cc204c1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs @@ -27,7 +27,7 @@ public static ImmutableArray LoadNearbyAssemblies(IEnumerable } } - return assemblies.ToImmutableArray(); + return [.. assemblies]; } private static Assembly TryLoadNearbyAssembly(string assemblySimpleName) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/AddImports/AbstractAddImportsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/AddImports/AbstractAddImportsService.cs index 3d07171076534..d5524bf1a9afe 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/AddImports/AbstractAddImportsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/AddImports/AbstractAddImportsService.cs @@ -112,7 +112,7 @@ public SyntaxNode GetImportContainer(SyntaxNode root, SyntaxNode? contextLocatio switch (import) { - case TExternSyntax _: + case TExternSyntax: return externContainer; case TUsingOrAliasSyntax u: if (IsAlias(u)) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/AddImports/IAddImportsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/AddImports/IAddImportsService.cs index 73dd939ef2389..9de5f16f87215 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/AddImports/IAddImportsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/AddImports/IAddImportsService.cs @@ -47,6 +47,6 @@ public static SyntaxNode AddImport( CancellationToken cancellationToken) { return service.AddImports(compilation, root, contextLocation, - SpecializedCollections.SingletonEnumerable(newImport), generator, options, cancellationToken); + [newImport], generator, options, cancellationToken); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.AbstractTypeInferrer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.AbstractTypeInferrer.cs index 42eb03714eb4d..4dc1aa16c05c2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.AbstractTypeInferrer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.AbstractTypeInferrer.cs @@ -70,7 +70,7 @@ protected IEnumerable GetTypes(SyntaxNode expression, bool ob } } - return SpecializedCollections.EmptyEnumerable(); + return []; } private ImmutableArray Filter(IEnumerable types, bool filterUnusable = true) @@ -84,9 +84,7 @@ protected IEnumerable CreateResult(SpecialType type, Nullable => CreateResult(Compilation.GetSpecialType(type).WithNullableAnnotation(nullableAnnotation)); protected static IEnumerable CreateResult(ITypeSymbol type) - => type == null - ? SpecializedCollections.EmptyCollection() - : SpecializedCollections.SingletonEnumerable(new TypeInferenceInfo(type)); + => type == null ? [] : [new TypeInferenceInfo(type)]; protected static IEnumerable ExpandParamsParameter(IParameterSymbol parameterSymbol) { @@ -114,12 +112,10 @@ protected static IEnumerable GetCollectionElementType(INamedT var elementType = parameters.ElementAtOrDefault(0); if (elementType != null) - { - return SpecializedCollections.SingletonCollection(new TypeInferenceInfo(elementType)); - } + return [new TypeInferenceInfo(elementType)]; } - return SpecializedCollections.EmptyEnumerable(); + return []; } protected static bool IsEnumHasFlag(ISymbol symbol) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/ParsedDocument.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/ParsedDocument.cs index 5eb4b3ae50a8c..b3f3db3086555 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/ParsedDocument.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/ParsedDocument.cs @@ -73,9 +73,7 @@ public IEnumerable GetChanges(in ParsedDocument oldDocument) Contract.ThrowIfFalse(Id == oldDocument.Id); if (Text == oldDocument.Text || SyntaxTree == oldDocument.SyntaxTree) - { - return SpecializedCollections.EmptyEnumerable(); - } + return []; var textChanges = Text.GetTextChanges(oldDocument.Text); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/LanguageServiceMetadata.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/LanguageServiceMetadata.cs index 4b680ccd43875..a924e71101407 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/LanguageServiceMetadata.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/LanguageServiceMetadata.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host.Mef { @@ -13,7 +14,16 @@ internal class LanguageServiceMetadata(IDictionary data) : ILang { public string Language { get; } = (string)data[nameof(ExportLanguageServiceAttribute.Language)]; public string ServiceType { get; } = (string)data[nameof(ExportLanguageServiceAttribute.ServiceType)]; - public string Layer { get; } = (string)data[nameof(ExportLanguageServiceAttribute.Layer)]; + + // Workaround for https://github.com/dotnet/roslynator/issues/1437. + // ExportLanguageServiceAttribute requires the layer to always be specified. + // + // However, if the service is exported like so, it will not be available. + // [Export(typeof(ILanguageService))] + // [ExportMetadata("Language", LanguageNames.CSharp)] + // [ExportMetadata("ServiceType", "type name")] + // + public string Layer { get; } = (string?)data.GetValueOrDefault(nameof(ExportLanguageServiceAttribute.Layer)) ?? ServiceLayer.Default; public IReadOnlyList WorkspaceKinds { get; } = (IReadOnlyList)data[ #if CODE_STYLE diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/OrderableMetadata.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/OrderableMetadata.cs index 72c2a9a8c5a5d..0ca604272fc80 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/OrderableMetadata.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/OrderableMetadata.cs @@ -31,8 +31,8 @@ public OrderableMetadata(IDictionary data) public OrderableMetadata(string? name, IEnumerable? after = null, IEnumerable? before = null) { - this.AfterTyped = after ?? SpecializedCollections.EmptyEnumerable(); - this.BeforeTyped = before ?? SpecializedCollections.EmptyEnumerable(); + this.AfterTyped = after ?? []; + this.BeforeTyped = before ?? []; this.Name = name; } } diff --git a/src/Workspaces/TestAnalyzerReference/HelloWorldGenerator.cs b/src/Workspaces/TestAnalyzerReference/HelloWorldGenerator.cs index cf5e8866d86ad..ec7589b6ddc50 100644 --- a/src/Workspaces/TestAnalyzerReference/HelloWorldGenerator.cs +++ b/src/Workspaces/TestAnalyzerReference/HelloWorldGenerator.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.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -22,26 +23,37 @@ public void Initialize(GeneratorInitializationContext context) public void Execute(GeneratorExecutionContext context) { - context.AddSource(GeneratedEnglishClassName, SourceText.From(@" -/// is a simple class to fetch the classic message. -internal class " + GeneratedEnglishClassName + @" -{ - public static string GetMessage() - { - return ""Hello, World!""; - } -} -", encoding: Encoding.UTF8)); + context.AddSource(GeneratedEnglishClassName, SourceText.From($$""" + /// is a simple class to fetch the classic message. + internal class {{GeneratedEnglishClassName}} + { + public static string GetMessage() + { + return "Hello, World!"; + } + } + """, encoding: Encoding.UTF8)); - context.AddSource(GeneratedSpanishClassName, SourceText.From(@" -internal class " + GeneratedSpanishClassName + @" -{ - public static string GetMessage() - { - return ""Hola, Mundo!""; - } -} -", encoding: Encoding.UTF8)); + context.AddSource(GeneratedSpanishClassName, SourceText.From($$""" + internal class {{GeneratedSpanishClassName}} + { + public static string GetMessage() + { + return "Hola, Mundo!"; + } + } + """, encoding: Encoding.UTF8)); + + context.AddSource(GeneratedEnglishClassName + "WithTime", SourceText.From($$""" + /// is a simple class to fetch the classic message. + internal class {{GeneratedEnglishClassName}}WithTime + { + public static string GetMessage() + { + return "Hello, World @ {{DateTime.UtcNow.ToLocalTime().ToLongTimeString()}}"; + } + } + """, encoding: Encoding.UTF8)); context.AddSource($"{GeneratedFolderName}/{GeneratedFolderClassName}", $$""" class {{GeneratedFolderClassName}} { } diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb index b5eabd5569fa1..25ad7483d6050 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb @@ -808,15 +808,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Optional modifiers As DeclarationModifiers = Nothing, Optional statements As IEnumerable(Of SyntaxNode) = Nothing) As SyntaxNode + Return OperatorDeclaration(GetOperatorName(kind), isImplicitConversion:=kind = OperatorKind.ImplicitConversion, parameters, returnType, accessibility, modifiers, statements) + End Function + + Private Protected Overrides Function OperatorDeclaration(operatorName As String, isImplicitConversion As Boolean, Optional parameters As IEnumerable(Of SyntaxNode) = Nothing, Optional returnType As SyntaxNode = Nothing, Optional accessibility As Accessibility = Accessibility.NotApplicable, Optional modifiers As DeclarationModifiers = Nothing, Optional statements As IEnumerable(Of SyntaxNode) = Nothing) As SyntaxNode Dim statement As OperatorStatementSyntax Dim asClause = If(returnType IsNot Nothing, SyntaxFactory.SimpleAsClause(DirectCast(returnType, TypeSyntax)), Nothing) Dim parameterList = GetParameterList(parameters) - Dim operatorToken = SyntaxFactory.Token(GetTokenKind(kind)) + Dim vbSyntaxKind As SyntaxKind = VisualBasic.SyntaxFacts.GetOperatorKind(operatorName) + Dim operatorToken = SyntaxFactory.Token(vbSyntaxKind) Dim modifierList As SyntaxTokenList = GetModifierList(accessibility, modifiers And s_methodModifiers, declaration:=Nothing, DeclarationKind.Operator) - If kind = OperatorKind.ImplicitConversion OrElse kind = OperatorKind.ExplicitConversion Then + If vbSyntaxKind = SyntaxKind.CTypeKeyword Then modifierList = modifierList.Add(SyntaxFactory.Token( - If(kind = OperatorKind.ImplicitConversion, SyntaxKind.WideningKeyword, SyntaxKind.NarrowingKeyword))) + If(isImplicitConversion, SyntaxKind.WideningKeyword, SyntaxKind.NarrowingKeyword))) statement = SyntaxFactory.OperatorStatement( attributeLists:=Nothing, modifiers:=modifierList, operatorToken:=operatorToken, parameterList:=parameterList, asClause:=asClause) @@ -837,53 +842,54 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration End If End Function - Private Shared Function GetTokenKind(kind As OperatorKind) As SyntaxKind + Private Shared Function GetOperatorName(kind As OperatorKind) As String Select Case kind - Case OperatorKind.ImplicitConversion, - OperatorKind.ExplicitConversion - Return SyntaxKind.CTypeKeyword + Case OperatorKind.ImplicitConversion + Return WellKnownMemberNames.ImplicitConversionName + Case OperatorKind.ExplicitConversion + Return WellKnownMemberNames.ExplicitConversionName Case OperatorKind.Addition - Return SyntaxKind.PlusToken + Return WellKnownMemberNames.AdditionOperatorName Case OperatorKind.BitwiseAnd - Return SyntaxKind.AndKeyword + Return WellKnownMemberNames.BitwiseAndOperatorName Case OperatorKind.BitwiseOr - Return SyntaxKind.OrKeyword + Return WellKnownMemberNames.BitwiseOrOperatorName Case OperatorKind.Division - Return SyntaxKind.SlashToken + Return WellKnownMemberNames.DivisionOperatorName Case OperatorKind.Equality - Return SyntaxKind.EqualsToken + Return WellKnownMemberNames.EqualityOperatorName Case OperatorKind.ExclusiveOr - Return SyntaxKind.XorKeyword + Return WellKnownMemberNames.ExclusiveOrOperatorName Case OperatorKind.False - Return SyntaxKind.IsFalseKeyword + Return WellKnownMemberNames.FalseOperatorName Case OperatorKind.GreaterThan - Return SyntaxKind.GreaterThanToken + Return WellKnownMemberNames.GreaterThanOperatorName Case OperatorKind.GreaterThanOrEqual - Return SyntaxKind.GreaterThanEqualsToken + Return WellKnownMemberNames.GreaterThanOrEqualOperatorName Case OperatorKind.Inequality - Return SyntaxKind.LessThanGreaterThanToken + Return WellKnownMemberNames.InequalityOperatorName Case OperatorKind.LeftShift - Return SyntaxKind.LessThanLessThanToken + Return WellKnownMemberNames.LeftShiftOperatorName Case OperatorKind.LessThan - Return SyntaxKind.LessThanToken + Return WellKnownMemberNames.LessThanOperatorName Case OperatorKind.LessThanOrEqual - Return SyntaxKind.LessThanEqualsToken + Return WellKnownMemberNames.LessThanOrEqualOperatorName Case OperatorKind.LogicalNot - Return SyntaxKind.NotKeyword + Return WellKnownMemberNames.LogicalNotOperatorName Case OperatorKind.Modulus - Return SyntaxKind.ModKeyword + Return WellKnownMemberNames.ModulusOperatorName Case OperatorKind.Multiply - Return SyntaxKind.AsteriskToken + Return WellKnownMemberNames.MultiplyOperatorName Case OperatorKind.RightShift - Return SyntaxKind.GreaterThanGreaterThanToken + Return WellKnownMemberNames.RightShiftOperatorName Case OperatorKind.Subtraction - Return SyntaxKind.MinusToken + Return WellKnownMemberNames.SubtractionOperatorName Case OperatorKind.True - Return SyntaxKind.IsTrueKeyword + Return WellKnownMemberNames.TrueOperatorName Case OperatorKind.UnaryNegation - Return SyntaxKind.MinusToken + Return WellKnownMemberNames.UnaryNegationOperatorName Case OperatorKind.UnaryPlus - Return SyntaxKind.PlusToken + Return WellKnownMemberNames.UnaryPlusOperatorName Case Else Throw New ArgumentException($"Operator {kind} cannot be generated in Visual Basic.") End Select @@ -1675,6 +1681,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return attr.WithTarget(Nothing) End Function + Friend Overrides Function GetPrimaryConstructorParameterList(declaration As SyntaxNode) As SyntaxNode + Return Nothing + End Function + Friend Overrides Function GetTypeInheritance(declaration As SyntaxNode) As ImmutableArray(Of SyntaxNode) Dim typeDecl = TryCast(declaration, TypeBlockSyntax) If typeDecl Is Nothing Then diff --git a/src/Workspaces/VisualBasic/Portable/ObsoleteSymbol/VisualBasicObsoleteSymbolService.vb b/src/Workspaces/VisualBasic/Portable/ObsoleteSymbol/VisualBasicObsoleteSymbolService.vb new file mode 100644 index 0000000000000..f20879b1fdce4 --- /dev/null +++ b/src/Workspaces/VisualBasic/Portable/ObsoleteSymbol/VisualBasicObsoleteSymbolService.vb @@ -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. + +Imports System.Composition +Imports System.Threading +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.ObsoleteSymbol +Imports Microsoft.CodeAnalysis.PooledObjects +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.ObsoleteSymbol + + <[Shared]> + Friend Class VisualBasicObsoleteSymbolService + Inherits AbstractObsoleteSymbolService + + + + Public Sub New() + MyBase.New(SyntaxKind.DimKeyword) + End Sub + + Protected Overrides Sub ProcessDimKeyword(ByRef result As ArrayBuilder(Of TextSpan), semanticModel As SemanticModel, token As SyntaxToken, cancellationToken As CancellationToken) + Dim localDeclaration = TryCast(token.Parent, LocalDeclarationStatementSyntax) + If localDeclaration Is Nothing Then + Return + End If + + If localDeclaration.Declarators.Count <> 1 Then + Return + End If + + Dim declarator = localDeclaration.Declarators(0) + If declarator.AsClause IsNot Nothing Then + ' This is an explicitly typed variable, so no need to mark 'Dim' obsolete + Return + End If + + If declarator.Names.Count <> 1 Then + ' More than one variable is declared + Return + End If + + ' Only one variable is declared + Dim localSymbol = TryCast(semanticModel.GetDeclaredSymbol(declarator.Names(0), cancellationToken), ILocalSymbol) + If IsSymbolObsolete(localSymbol?.Type) Then + If result Is Nothing Then + result = ArrayBuilder(Of TextSpan).GetInstance() + End If + + result.Add(token.Span) + End If + End Sub + End Class +End Namespace diff --git a/src/Workspaces/VisualBasic/Portable/xlf/VBWorkspaceResources.de.xlf b/src/Workspaces/VisualBasic/Portable/xlf/VBWorkspaceResources.de.xlf index 5c1ddf495702f..b7655c60b9bec 100644 --- a/src/Workspaces/VisualBasic/Portable/xlf/VBWorkspaceResources.de.xlf +++ b/src/Workspaces/VisualBasic/Portable/xlf/VBWorkspaceResources.de.xlf @@ -69,7 +69,7 @@ The event handler to associate with the event. This may take the form of { AddressOf <eventHandler> | <delegate> | <lambdaExpression> }. - Der Ereignishandler, dem das Ereignis zugeordnet wird. Dies kann die Form { AddressOf <Ereignishandler> | <Delegat> | <Lambdaausdruck> } haben. + Der Ereignishandler, dem das Ereignis zugeordnet wird. Dies kann die Form { AddressOf <eventHandler> | <delegate> | <lambdaExpression> } haben. @@ -184,7 +184,7 @@ The event handler to disassociate from the event. This may take the form of { AddressOf <eventHandler> | <delegate> }. - Der Ereignishandler, von dem das Ereignis getrennt wird. Dies kann die Form { AddressOf <Ereignishandler> | <Delegat> } haben. + Der Ereignishandler, von dem das Ereignis getrennt wird. Dies kann die Form { AddressOf <eventHandler> | <delegate> } haben. diff --git a/src/Workspaces/VisualBasicTest/CodeGeneration/SyntaxGeneratorTests.vb b/src/Workspaces/VisualBasicTest/CodeGeneration/SyntaxGeneratorTests.vb index 57f42839a7ff9..97ab3a5e94c6e 100644 --- a/src/Workspaces/VisualBasicTest/CodeGeneration/SyntaxGeneratorTests.vb +++ b/src/Workspaces/VisualBasicTest/CodeGeneration/SyntaxGeneratorTests.vb @@ -1080,6 +1080,23 @@ End Operator") End Operator") End Sub + + Public Sub TestOperatorDeclarationSymbolOverload() + Dim tree = VisualBasicSyntaxTree.ParseText( +" +Public Class C + Shared Operator +(c1 As C, c2 As C) As C + End Operator +End Class") + Dim compilation = VisualBasicCompilation.Create("AssemblyName", syntaxTrees:={tree}) + + Dim additionOperatorSymbol = DirectCast(compilation.GetTypeByMetadataName("C").GetMembers(WellKnownMemberNames.AdditionOperatorName).Single(), IMethodSymbol) + VerifySyntax(Of OperatorBlockSyntax)( + Generator.OperatorDeclaration(additionOperatorSymbol), +"Public Shared Operator +(c1 As Global.C, c2 As Global.C) As Global.C +End Operator") + End Sub + Public Sub MethodDeclarationCanRoundTrip() Dim tree = VisualBasicSyntaxTree.ParseText(